예제 #1
0
class SubstituentTable(QWidget):
    def __init__(self, parent=None, singleSelect=False):
        super().__init__(parent)

        layout = QGridLayout(self)

        self.table = QTableWidget()

        self.table.setColumnCount(3)
        self.table.setHorizontalHeaderLabels(
            ['name', 'conformers', 'conf. angle'])

        self.add_subs()

        for i in range(0, 3):
            self.table.resizeColumnToContents(i)

        self.table.horizontalHeader().setStretchLastSection(False)
        self.table.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.Fixed)
        self.table.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.Fixed)
        self.table.horizontalHeader().setSectionResizeMode(
            2, QHeaderView.Stretch)

        self.table.setSortingEnabled(True)
        self.table.setSelectionBehavior(QTableWidget.SelectRows)
        if singleSelect:
            self.table.setSelectionMode(QTableWidget.SingleSelection)
        self.table.setEditTriggers(QTableWidget.NoEditTriggers)

        self.filterEdit = QLineEdit()
        self.filterEdit.textChanged.connect(self.apply_filter)
        self.filterEdit.setClearButtonEnabled(True)

        self.filter_columns = QComboBox()
        self.filter_columns.addItem("name")
        self.filter_columns.addItem("conformers")
        self.filter_columns.addItem("conf. angle")
        self.filter_columns.currentTextChanged.connect(
            self.change_filter_method)

        self.name_regex_option = QComboBox()
        self.name_regex_option.addItem("case-insensitive")
        self.name_regex_option.addItem("case-sensitive")
        self.name_regex_option.currentTextChanged.connect(self.apply_filter)
        self.name_regex_option.setVisible(
            self.filter_columns.currentText() == "name")

        layout.addWidget(self.table, 0, 0, 1, 4)
        layout.addWidget(QLabel("filter based on"), 1, 0)
        layout.addWidget(self.filter_columns, 1, 1)
        layout.addWidget(self.name_regex_option, 1, 2)
        layout.addWidget(self.filterEdit, 1, 3)

        self.change_filter_method("name")

    def change_filter_method(self, text):
        if text == "name":
            self.filterEdit.setToolTip("name regex")
            self.name_regex_option.setVisible(True)
        elif text == "conformers":
            self.filterEdit.setToolTip("number of conformers")
            self.name_regex_option.setVisible(False)
        elif text == "conf. angle":
            self.filterEdit.setToolTip("angle between conformers")
            self.name_regex_option.setVisible(False)

        self.apply_filter()

    def apply_filter(self, *args):
        text = self.filterEdit.text()
        if text:
            if self.filter_columns.currentText() == "name":
                m = QRegularExpression(text)
                if m.isValid():
                    if self.name_regex_option.currentText(
                    ) == "case-insensitive":
                        m.setPatternOptions(
                            QRegularExpression.CaseInsensitiveOption)

                    m.optimize()
                    filter = lambda row_num: m.match(
                        self.table.item(row_num, 0).text()).hasMatch()
                else:
                    return

            elif self.filter_columns.currentText() == "conformers":
                if text.isdigit():
                    filter = lambda row_num: int(
                        self.table.item(row_num, 1).text()) == int(text)
                else:
                    filter = lambda row: True

            elif self.filter_columns.currentText() == "conf. angle":
                if text.isdigit():
                    filter = lambda row_num: int(
                        self.table.item(row_num, 2).text()) == int(text)
                else:
                    filter = lambda row: True

        else:
            filter = lambda row: True

        for i in range(0, self.table.rowCount()):
            self.table.setRowHidden(i, not filter(i))

    def add_subs(self):
        from AaronTools.substituent import Substituent

        names = []

        for lib in [Substituent.AARON_LIBS, Substituent.BUILTIN]:
            if not os.path.exists(lib):
                continue
            for ring in os.listdir(lib):
                name, ext = os.path.splitext(ring)
                if not any(".%s" % x == ext for x in read_types):
                    continue

                if name in names:
                    continue

                names.append(name)

                geom = Geometry(
                    os.path.join(lib, ring),
                    refresh_connected=False,
                    refresh_ranks=False,
                )

                conf_info = re.search(r"CF:(\d+),(\d+)", geom.comment)

                row = self.table.rowCount()
                self.table.insertRow(row)
                self.table.setItem(row, 0, QTableWidgetItem(name))

                #the next two items are integers - need to initialize then setData so they sort and display correctly
                conf_num = QTableWidgetItem()
                conf_num.setData(Qt.DisplayRole, conf_info.group(1))
                self.table.setItem(row, 1, conf_num)

                conf_angle = QTableWidgetItem()
                conf_angle.setData(Qt.DisplayRole, conf_info.group(2))
                self.table.setItem(row, 2, conf_angle)

        self.substituent_list = names
예제 #2
0
class RingTable(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        layout = QGridLayout(self)

        self.table = QTableWidget()

        self.table.setColumnCount(1)
        self.table.setHorizontalHeaderLabels(['name'])

        self.add_rings()

        for i in range(0, 1):
            self.table.resizeColumnToContents(i)

        self.table.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.Stretch)

        self.table.setSortingEnabled(True)
        self.table.setSelectionBehavior(QTableWidget.SelectRows)
        self.table.setEditTriggers(QTableWidget.NoEditTriggers)

        self.filterEdit = QLineEdit()
        self.filterEdit.textChanged.connect(self.apply_filter)
        self.filterEdit.setClearButtonEnabled(True)

        self.filter_columns = QComboBox()
        self.filter_columns.addItem("name")
        self.filter_columns.currentTextChanged.connect(
            self.change_filter_method)

        self.name_regex_option = QComboBox()
        self.name_regex_option.addItem("case-insensitive")
        self.name_regex_option.addItem("case-sensitive")
        self.name_regex_option.currentTextChanged.connect(self.apply_filter)
        self.name_regex_option.setVisible(
            self.filter_columns.currentText() == "name")

        layout.addWidget(self.table, 0, 0, 1, 4)
        layout.addWidget(QLabel("filter based on"), 1, 0)
        layout.addWidget(self.filter_columns, 1, 1)
        layout.addWidget(self.name_regex_option, 1, 2)
        layout.addWidget(self.filterEdit, 1, 3)

        self.change_filter_method("name")

    def change_filter_method(self, text):
        if text == "name":
            self.filterEdit.setToolTip("name regex")
            self.name_regex_option.setVisible(True)

        self.apply_filter()

    def apply_filter(self, *args):
        text = self.filterEdit.text()
        if text:
            if self.filter_columns.currentText() == "name":
                m = QRegularExpression(text)
                if m.isValid():
                    if self.name_regex_option.currentText(
                    ) == "case-insensitive":
                        m.setPatternOptions(
                            QRegularExpression.CaseInsensitiveOption)

                    m.optimize()
                    filter = lambda row_num: m.match(
                        self.table.item(row_num, 0).text()).hasMatch()
                else:
                    return

        else:
            filter = lambda row: True

        for i in range(0, self.table.rowCount()):
            self.table.setRowHidden(i, not filter(i))

    def add_rings(self):
        from AaronTools.ring import Ring

        names = []
        for lib in [Ring.AARON_LIBS, Ring.BUILTIN]:
            if not os.path.exists(lib):
                continue
            for ring in os.listdir(lib):
                name, ext = os.path.splitext(ring)
                if not any(".%s" % x == ext for x in read_types):
                    continue

                if name in names:
                    continue

                names.append(name)

                row = self.table.rowCount()
                self.table.insertRow(row)
                self.table.setItem(row, 0, QTableWidgetItem(name))

        self.ring_list = names
예제 #3
0
class ConeAngle(ToolInstance):

    help = "https://github.com/QChASM/SEQCROW/wiki/Cone-Angle-Tool"

    def __init__(self, session, name):
        super().__init__(session, name)

        from chimerax.ui import MainToolWindow
        self.tool_window = MainToolWindow(self)

        self.settings = _ConeAngleSettings(self.session, name)

        self.ligands = dict()

        self._build_ui()

    def _build_ui(self):
        layout = QFormLayout()

        self.cone_option = QComboBox()
        self.cone_option.addItems(["Tolman (Unsymmetrical)", "Exact"])
        ndx = self.cone_option.findText(self.settings.cone_option,
                                        Qt.MatchExactly)
        self.cone_option.setCurrentIndex(ndx)
        layout.addRow("method:", self.cone_option)

        self.radii_option = QComboBox()
        self.radii_option.addItems(["Bondi", "UMN"])
        ndx = self.radii_option.findText(self.settings.radii, Qt.MatchExactly)
        self.radii_option.setCurrentIndex(ndx)
        layout.addRow("radii:", self.radii_option)

        self.display_cone = QCheckBox()
        self.display_cone.setChecked(self.settings.display_cone)
        layout.addRow("show cone:", self.display_cone)

        self.display_radii = QCheckBox()
        self.display_radii.setChecked(self.settings.display_radii)
        layout.addRow("show radii:", self.display_radii)

        set_ligand_button = QPushButton("set ligand to current selection")
        set_ligand_button.clicked.connect(self.set_ligand)
        layout.addRow(set_ligand_button)
        self.set_ligand_button = set_ligand_button

        calc_cone_button = QPushButton(
            "calculate cone angle for ligand on selected center")
        calc_cone_button.clicked.connect(self.calc_cone)
        layout.addRow(calc_cone_button)
        self.calc_cone_button = calc_cone_button

        remove_cone_button = QPushButton("remove cone visualizations")
        remove_cone_button.clicked.connect(self.del_cone)
        layout.addRow(remove_cone_button)
        self.remove_cone_button = remove_cone_button

        self.table = QTableWidget()
        self.table.setColumnCount(3)
        self.table.setHorizontalHeaderLabels([
            'model',
            'center',
            'cone angle (°)',
        ])

        self.table.setSelectionBehavior(QTableWidget.SelectRows)
        self.table.setEditTriggers(QTableWidget.NoEditTriggers)
        self.table.resizeColumnToContents(0)
        self.table.resizeColumnToContents(1)
        self.table.resizeColumnToContents(2)
        self.table.horizontalHeader().setSectionResizeMode(
            2, QHeaderView.Stretch)
        layout.addRow(self.table)

        menu = QMenuBar()

        export = menu.addMenu("&Export")

        clear = QAction("Clear data table", self.tool_window.ui_area)
        clear.triggered.connect(self.clear_table)
        export.addAction(clear)

        copy = QAction("&Copy CSV to clipboard", self.tool_window.ui_area)
        copy.triggered.connect(self.copy_csv)
        shortcut = QKeySequence(Qt.CTRL + Qt.Key_C)
        copy.setShortcut(shortcut)
        export.addAction(copy)

        save = QAction("&Save CSV...", self.tool_window.ui_area)
        save.triggered.connect(self.save_csv)
        #this shortcut interferes with main window's save shortcut
        #I've tried different shortcut contexts to no avail
        #thanks Qt...
        #shortcut = QKeySequence(Qt.CTRL + Qt.Key_S)
        #save.setShortcut(shortcut)
        #save.setShortcutContext(Qt.WidgetShortcut)
        export.addAction(save)

        delimiter = export.addMenu("Delimiter")

        comma = QAction("comma", self.tool_window.ui_area, checkable=True)
        comma.setChecked(self.settings.delimiter == "comma")
        comma.triggered.connect(lambda *args, delim="comma": self.settings.
                                __setattr__("delimiter", delim))
        delimiter.addAction(comma)

        tab = QAction("tab", self.tool_window.ui_area, checkable=True)
        tab.setChecked(self.settings.delimiter == "tab")
        tab.triggered.connect(lambda *args, delim="tab": self.settings.
                              __setattr__("delimiter", delim))
        delimiter.addAction(tab)

        space = QAction("space", self.tool_window.ui_area, checkable=True)
        space.setChecked(self.settings.delimiter == "space")
        space.triggered.connect(lambda *args, delim="space": self.settings.
                                __setattr__("delimiter", delim))
        delimiter.addAction(space)

        semicolon = QAction("semicolon",
                            self.tool_window.ui_area,
                            checkable=True)
        semicolon.setChecked(self.settings.delimiter == "semicolon")
        semicolon.triggered.connect(lambda *args, delim="semicolon": self.
                                    settings.__setattr__("delimiter", delim))
        delimiter.addAction(semicolon)

        add_header = QAction("&Include CSV header",
                             self.tool_window.ui_area,
                             checkable=True)
        add_header.setChecked(self.settings.include_header)
        add_header.triggered.connect(self.header_check)
        export.addAction(add_header)

        comma.triggered.connect(
            lambda *args, action=tab: action.setChecked(False))
        comma.triggered.connect(
            lambda *args, action=space: action.setChecked(False))
        comma.triggered.connect(
            lambda *args, action=semicolon: action.setChecked(False))

        tab.triggered.connect(
            lambda *args, action=comma: action.setChecked(False))
        tab.triggered.connect(
            lambda *args, action=space: action.setChecked(False))
        tab.triggered.connect(
            lambda *args, action=semicolon: action.setChecked(False))

        space.triggered.connect(
            lambda *args, action=comma: action.setChecked(False))
        space.triggered.connect(
            lambda *args, action=tab: action.setChecked(False))
        space.triggered.connect(
            lambda *args, action=semicolon: action.setChecked(False))

        semicolon.triggered.connect(
            lambda *args, action=comma: action.setChecked(False))
        semicolon.triggered.connect(
            lambda *args, action=tab: action.setChecked(False))
        semicolon.triggered.connect(
            lambda *args, action=space: action.setChecked(False))

        menu.setNativeMenuBar(False)
        self._menu = menu
        layout.setMenuBar(menu)
        menu.setVisible(True)

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)

    def clear_table(self):
        are_you_sure = QMessageBox.question(
            None,
            "Clear table?",
            "Are you sure you want to clear the data table?",
        )
        if are_you_sure != QMessageBox.Yes:
            return
        self.table.setRowCount(0)

    def header_check(self, state):
        """user has [un]checked the 'include header' option on the menu"""
        if state:
            self.settings.include_header = True
        else:
            self.settings.include_header = False

    def get_csv(self):
        if self.settings.delimiter == "comma":
            delim = ","
        elif self.settings.delimiter == "space":
            delim = " "
        elif self.settings.delimiter == "tab":
            delim = "\t"
        elif self.settings.delimiter == "semicolon":
            delim = ";"

        if self.settings.include_header:
            s = delim.join(["model", "center_atom", "cone_angle"])
            s += "\n"
        else:
            s = ""

        for i in range(0, self.table.rowCount()):
            s += delim.join([
                item.data(Qt.DisplayRole)
                for item in [self.table.item(i, j) for j in range(0, 3)]
            ])
            s += "\n"

        return s

    def copy_csv(self):
        app = QApplication.instance()
        clipboard = app.clipboard()
        csv = self.get_csv()
        clipboard.setText(csv)
        self.session.logger.status("copied to clipboard")

    def save_csv(self):
        """save data on current tab to CSV file"""
        filename, _ = QFileDialog.getSaveFileName(filter="CSV Files (*.csv)")
        if filename:
            s = self.get_csv()

            with open(filename, 'w') as f:
                f.write(s.strip())

            self.session.logger.status("saved to %s" % filename)

    def del_cone(self):
        for model in self.session.models.list(type=Generic3DModel):
            if model.name.startswith("Cone angle"):
                model.delete()

    def set_ligand(self, *args):
        self.ligands = {}
        for atom in selected_atoms(self.session):
            if atom.structure not in self.ligands:
                self.ligands[atom.structure] = []
            self.ligands[atom.structure].append(atom)
        self.session.logger.status("set ligand to current selection")

    def calc_cone(self, *args):
        self.settings.cone_option = self.cone_option.currentText()
        self.settings.radii = self.radii_option.currentText()
        self.settings.display_radii = self.display_radii.checkState(
        ) == Qt.Checked
        self.settings.display_cone = self.display_cone.checkState(
        ) == Qt.Checked

        if self.cone_option.currentText() == "Tolman (Unsymmetrical)":
            method = "tolman"
        else:
            method = self.cone_option.currentText()

        radii = self.radii_option.currentText()
        return_cones = self.display_cone.checkState() == Qt.Checked
        display_radii = self.display_radii.checkState() == Qt.Checked

        # self.table.setRowCount(0)

        for center_atom in selected_atoms(self.session):
            rescol = ResidueCollection(center_atom.structure)
            at_center = rescol.find_exact(AtomSpec(center_atom.atomspec))[0]
            if center_atom.structure in self.ligands:
                comp = Component(
                    rescol.find([
                        AtomSpec(atom.atomspec)
                        for atom in self.ligands[center_atom.structure]
                    ]),
                    to_center=rescol.find_exact(AtomSpec(
                        center_atom.atomspec)),
                    key_atoms=rescol.find(BondedTo(at_center)),
                )
            else:
                comp = Component(
                    rescol.find(NotAny(at_center)),
                    to_center=rescol.find_exact(AtomSpec(
                        center_atom.atomspec)),
                    key_atoms=rescol.find(BondedTo(at_center)),
                )

            cone_angle = comp.cone_angle(
                center=rescol.find(AtomSpec(center_atom.atomspec)),
                method=method,
                radii=radii,
                return_cones=return_cones,
            )

            if return_cones:
                cone_angle, cones = cone_angle
                s = ".transparency 0.5\n"
                for cone in cones:
                    apex, base, radius = cone
                    s += ".cone   %6.3f %6.3f %6.3f   %6.3f %6.3f %6.3f   %.3f open\n" % (
                        *apex, *base, radius)

                stream = BytesIO(bytes(s, "utf-8"))
                bild_obj, status = read_bild(self.session, stream,
                                             "Cone angle %s" % center_atom)

                self.session.models.add(bild_obj, parent=center_atom.structure)

            if display_radii:
                s = ".note radii\n"
                s += ".transparency 75\n"
                color = None
                for atom in comp.atoms:
                    chix_atom = atom.chix_atom
                    if radii.lower() == "umn":
                        r = VDW_RADII[chix_atom.element.name]
                    elif radii.lower() == "bondi":
                        r = BONDI_RADII[chix_atom.element.name]

                    if color is None or chix_atom.color != color:
                        color = chix_atom.color
                        rgb = [x / 255. for x in chix_atom.color]
                        rgb.pop(-1)

                        s += ".color %f %f %f\n" % tuple(rgb)

                    s += ".sphere %f %f %f %f\n" % (*chix_atom.coord, r)

                stream = BytesIO(bytes(s, "utf-8"))
                bild_obj, status = read_bild(self.session, stream,
                                             "Cone angle radii")

                self.session.models.add(bild_obj, parent=center_atom.structure)

            row = self.table.rowCount()
            self.table.insertRow(row)

            name = QTableWidgetItem()
            name.setData(Qt.DisplayRole, center_atom.structure.name)
            self.table.setItem(row, 0, name)

            center = QTableWidgetItem()
            center.setData(Qt.DisplayRole, center_atom.atomspec)
            self.table.setItem(row, 1, center)

            ca = QTableWidgetItem()
            ca.setData(Qt.DisplayRole, "%.2f" % cone_angle)
            self.table.setItem(row, 2, ca)

            self.table.resizeColumnToContents(0)
            self.table.resizeColumnToContents(1)
            self.table.resizeColumnToContents(2)
예제 #4
0
class LigandTable(QWidget):
    def __init__(
        self,
        parent=None,
        singleSelect=False,
        maxDenticity=None,
        include_substituents=False,
    ):
        super().__init__(parent)

        self._include_substituents = include_substituents

        layout = QGridLayout(self)

        self.table = QTableWidget()

        self.table.setColumnCount(3)
        self.table.setHorizontalHeaderLabels(
            ['name', 'denticity', 'coordinating elements'])

        self.add_ligands(maxDenticity)

        for i in range(0, 3):
            self.table.resizeColumnToContents(i)

        self.table.horizontalHeader().setStretchLastSection(False)
        self.table.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.Fixed)
        self.table.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.Fixed)
        self.table.horizontalHeader().setSectionResizeMode(
            2, QHeaderView.Stretch)

        self.table.setSortingEnabled(True)
        self.table.setSelectionBehavior(QTableWidget.SelectRows)
        if singleSelect:
            self.table.setSelectionMode(QTableWidget.SingleSelection)
        self.table.setEditTriggers(QTableWidget.NoEditTriggers)

        self.filterEdit = QLineEdit()
        self.filterEdit.textChanged.connect(self.apply_filter)
        self.filterEdit.setClearButtonEnabled(True)

        self.filter_columns = QComboBox()
        self.filter_columns.addItem("name")
        self.filter_columns.addItem("denticity")
        self.filter_columns.addItem("coordinating elements")
        self.filter_columns.currentTextChanged.connect(
            self.change_filter_method)

        self.name_regex_option = QComboBox()
        self.name_regex_option.addItem("case-insensitive")
        self.name_regex_option.addItem("case-sensitive")
        self.name_regex_option.currentTextChanged.connect(self.apply_filter)
        self.name_regex_option.setVisible(
            self.filter_columns.currentText() == "name")

        self.coordinating_elements_method = QComboBox()
        self.coordinating_elements_method.addItem("exactly")
        self.coordinating_elements_method.addItem("at least")
        self.coordinating_elements_method.currentTextChanged.connect(
            self.apply_filter)
        self.coordinating_elements_method.setVisible(
            self.filter_columns.currentText() == "coordinating elements")

        layout.addWidget(self.table, 0, 0, 1, 4)
        layout.addWidget(QLabel("filter based on"), 1, 0)
        layout.addWidget(self.filter_columns, 1, 1)
        layout.addWidget(self.coordinating_elements_method, 1, 2)
        layout.addWidget(self.name_regex_option, 1, 2)
        layout.addWidget(self.filterEdit, 1, 3)

        self.change_filter_method("name")

    def add_ligands(self, maxDenticity=None):
        from AaronTools.component import Component

        names = []

        for lib in [Component.AARON_LIBS, Component.BUILTIN]:
            if not os.path.exists(lib):
                continue
            for lig in os.listdir(lib):
                name, ext = os.path.splitext(lig)
                if not any(".%s" % x == ext for x in read_types):
                    continue

                if name in names:
                    continue

                names.append(name)

                geom = Geometry(
                    os.path.join(lib, lig),
                    refresh_connected=False,
                    refresh_ranks=False,
                )

                key_atoms = [geom.atoms[i] for i in geom.other["key_atoms"]]

                if maxDenticity and len(key_atoms) > maxDenticity:
                    continue

                row = self.table.rowCount()
                self.table.insertRow(row)
                self.table.setItem(row, 0, QTableWidgetItem(name))

                #this is an integer, so I need to initialize it then set the data
                denticity = QTableWidgetItem()
                denticity.setData(Qt.DisplayRole, len(key_atoms))
                self.table.setItem(row, 1, denticity)

                self.table.setItem(
                    row, 2,
                    QTableWidgetItem(", ".join(
                        sorted([atom.element for atom in key_atoms]))))

        if self._include_substituents:
            from AaronTools.substituent import Substituent

            for lib in [Substituent.AARON_LIBS, Substituent.BUILTIN]:
                if not os.path.exists(lib):
                    continue
                for sub in os.listdir(lib):
                    name, ext = os.path.splitext(sub)
                    if not any(".%s" % x == ext for x in read_types):
                        continue

                    if name in names:
                        continue

                    names.append(name)

                    geom = Geometry(
                        os.path.join(lib, sub),
                        refresh_connected=False,
                        refresh_ranks=False,
                    )

                    key_atoms = [geom.atoms[0]]

                    if maxDenticity and len(key_atoms) > maxDenticity:
                        continue

                    row = self.table.rowCount()
                    self.table.insertRow(row)
                    self.table.setItem(row, 0, QTableWidgetItem(name))

                    #this is an integer, so I need to initialize it then set the data
                    denticity = QTableWidgetItem()
                    denticity.setData(Qt.DisplayRole, len(key_atoms))
                    self.table.setItem(row, 1, denticity)

                    self.table.setItem(
                        row, 2,
                        QTableWidgetItem(", ".join(
                            sorted([atom.element for atom in key_atoms]))))

        self.ligand_list = names

    def change_filter_method(self, text):
        if text == "coordinating elements":
            self.filterEdit.setToolTip("comma and/or space delimited elements")
            self.coordinating_elements_method.setVisible(True)
            self.name_regex_option.setVisible(False)
        elif text == "name":
            self.filterEdit.setToolTip("name regex")
            self.coordinating_elements_method.setVisible(False)
            self.name_regex_option.setVisible(True)
        elif text == "denticity":
            self.filterEdit.setToolTip("number of key atoms")
            self.coordinating_elements_method.setVisible(False)
            self.name_regex_option.setVisible(False)

        self.apply_filter()

    def apply_filter(self, *args):
        text = self.filterEdit.text()
        if text:
            if self.filter_columns.currentText() == "name":
                m = QRegularExpression(text)
                if m.isValid():
                    if self.name_regex_option.currentText(
                    ) == "case-insensitive":
                        m.setPatternOptions(
                            QRegularExpression.CaseInsensitiveOption)

                    m.optimize()
                    filter = lambda row_num: m.match(
                        self.table.item(row_num, 0).text()).hasMatch()
                else:
                    return

            elif self.filter_columns.currentText() == "denticity":
                if text.isdigit():
                    filter = lambda row_num: int(
                        self.table.item(row_num, 1).text()) == int(text)
                else:
                    filter = lambda row: True

            elif self.filter_columns.currentText() == "coordinating elements":
                method = self.coordinating_elements_method.currentText()

                def filter(row_num):
                    row_key_atoms = [
                        item.strip() for item in self.table.item(
                            row_num, 2).text().split(',')
                    ]
                    search_atoms = []
                    for item in text.split():
                        for ele in item.split(','):
                            if ele.strip() != "":
                                search_atoms.append(ele)

                    if method == "exactly":
                        if all([row_key_atoms.count(element) == search_atoms.count(element) for element in set(search_atoms)]) and \
                            all([row_key_atoms.count(element) == search_atoms.count(element) for element in set(row_key_atoms)]):
                            return True
                        else:
                            return False

                    elif method == "at least":
                        if all([
                                row_key_atoms.count(element) >=
                                search_atoms.count(element)
                                for element in set(search_atoms)
                        ]):
                            return True
                        else:
                            return False

        else:
            filter = lambda row: True

        for i in range(0, self.table.rowCount()):
            self.table.setRowHidden(i, not filter(i))
예제 #5
0
class InformationWindow(QDialog):
    imageChanged = Signal(int, str)

    def __init__(self, id_, items, parent=None):
        super().__init__(parent)

        itemLabel = QLabel(self.tr("Item: "))
        descriptionLabel = QLabel(self.tr("Description: "))
        imageFileLabel = QLabel(self.tr("Image file: "))

        self.createButtons()

        self.itemText = QLabel()
        self.descriptionEditor = QTextEdit()

        self.imageFileEditor = QComboBox()
        self.imageFileEditor.setModel(items.relationModel(1))
        self.imageFileEditor.setModelColumn(
            items.relationModel(1).fieldIndex("file"))

        self.mapper = QDataWidgetMapper(self)
        self.mapper.setModel(items)
        self.mapper.setSubmitPolicy(QDataWidgetMapper.ManualSubmit)
        self.mapper.setItemDelegate(QSqlRelationalDelegate(self.mapper))
        self.mapper.addMapping(self.imageFileEditor, 1)
        self.mapper.addMapping(self.itemText, 2, b"text")
        self.mapper.addMapping(self.descriptionEditor, 3)
        self.mapper.setCurrentIndex(id_)

        self.descriptionEditor.textChanged.connect(self.enableButtons)
        self.imageFileEditor.currentIndexChanged.connect(self.enableButtons)

        formLayout = QFormLayout()
        formLayout.addRow(itemLabel, self.itemText)
        formLayout.addRow(imageFileLabel, self.imageFileEditor)
        formLayout.addRow(descriptionLabel, self.descriptionEditor)

        layout = QVBoxLayout()
        layout.addLayout(formLayout)
        layout.addWidget(self.buttonBox)
        self.setLayout(layout)

        self.itemId = id_
        self.displayedImage = self.imageFileEditor.currentText()

        self.setWindowFlags(Qt.Window)
        self.enableButtons(False)
        self.setWindowTitle(self.itemText.text())

    def id(self):
        return self.itemId

    @Slot()
    def revert(self):
        self.mapper.revert()
        self.enableButtons(False)

    @Slot()
    def submit(self):
        newImage = self.imageFileEditor.currentText()

        if self.displayedImage != newImage:
            self.displayedImage = newImage
            self.imageChanged.emit(self.itemId, newImage)

        self.mapper.submit()
        self.mapper.setCurrentIndex(self.itemId)

        self.enableButtons(False)

    def createButtons(self):

        self.closeButton = QPushButton(self.tr("&Close"))
        self.revertButton = QPushButton(self.tr("&Revert"))
        self.submitButton = QPushButton(self.tr("&Submit"))

        self.closeButton.setDefault(True)

        self.closeButton.clicked.connect(self.close)
        self.revertButton.clicked.connect(self.revert)
        self.submitButton.clicked.connect(self.submit)

        self.buttonBox = QDialogButtonBox(self)
        self.buttonBox.addButton(self.submitButton,
                                 QDialogButtonBox.AcceptRole)
        self.buttonBox.addButton(self.revertButton, QDialogButtonBox.ResetRole)
        self.buttonBox.addButton(self.closeButton, QDialogButtonBox.RejectRole)

    @Slot()
    @Slot(bool)
    def enableButtons(self, enable=True):
        self.revertButton.setEnabled(enable)
        self.submitButton.setEnabled(enable)
예제 #6
0
class BondEditor(ToolInstance):

    help = "https://github.com/QChASM/SEQCROW/wiki/Bond-Editor-Tool"
    SESSION_ENDURING = False
    SESSION_SAVE = False

    def __init__(self, session, name):
        super().__init__(session, name)

        self.tool_window = MainToolWindow(self)

        self.settings = _BondEditorSettings(session, name)

        self._build_ui()

    def _build_ui(self):
        layout = QGridLayout()

        tabs = QTabWidget()
        layout.addWidget(tabs)

        ts_bond_tab = QWidget()
        ts_options = QFormLayout(ts_bond_tab)

        self.tsbond_color = ColorButton(has_alpha_channel=False,
                                        max_size=(16, 16))
        self.tsbond_color.set_color(self.settings.tsbond_color)
        ts_options.addRow("color:", self.tsbond_color)

        self.tsbond_transparency = QSpinBox()
        self.tsbond_transparency.setRange(1, 99)
        self.tsbond_transparency.setValue(self.settings.tsbond_transparency)
        self.tsbond_transparency.setSuffix("%")
        ts_options.addRow("transparency:", self.tsbond_transparency)

        self.tsbond_radius = QDoubleSpinBox()
        self.tsbond_radius.setRange(0.01, 1)
        self.tsbond_radius.setDecimals(3)
        self.tsbond_radius.setSingleStep(0.005)
        self.tsbond_radius.setSuffix(" \u212B")
        self.tsbond_radius.setValue(self.settings.tsbond_radius)
        ts_options.addRow("radius:", self.tsbond_radius)

        draw_tsbonds = QPushButton("draw TS bonds on selected atoms/bonds")
        draw_tsbonds.clicked.connect(self.run_tsbond)
        ts_options.addRow(draw_tsbonds)
        self.draw_tsbonds = draw_tsbonds

        erase_tsbonds = QPushButton("erase selected TS bonds")
        erase_tsbonds.clicked.connect(self.run_erase_tsbond)
        ts_options.addRow(erase_tsbonds)
        self.erase_tsbonds = erase_tsbonds

        bond_tab = QWidget()
        bond_options = QFormLayout(bond_tab)

        self.bond_halfbond = QCheckBox()
        self.bond_halfbond.setChecked(self.settings.bond_halfbond)
        self.bond_halfbond.setToolTip(
            "each half of the bond will be colored according to the atom's color"
        )
        bond_options.addRow("half-bond:", self.bond_halfbond)

        self.bond_color = ColorButton(has_alpha_channel=True,
                                      max_size=(16, 16))
        self.bond_color.set_color(self.settings.bond_color)
        self.bond_color.setEnabled(
            self.bond_halfbond.checkState() != Qt.Checked)
        self.bond_halfbond.stateChanged.connect(
            lambda state, widget=self.bond_color: self.bond_color.setEnabled(
                state != Qt.Checked))
        bond_options.addRow("color:", self.bond_color)

        self.bond_radius = QDoubleSpinBox()
        self.bond_radius.setRange(0.01, 1)
        self.bond_radius.setDecimals(3)
        self.bond_radius.setSingleStep(0.005)
        self.bond_radius.setSuffix(" \u212B")
        self.bond_radius.setValue(self.settings.bond_radius)
        bond_options.addRow("radius:", self.bond_radius)

        draw_tsbonds = QPushButton("draw bond between selected atoms")
        draw_tsbonds.clicked.connect(self.run_bond)
        bond_options.addRow(draw_tsbonds)
        self.draw_tsbonds = draw_tsbonds

        erase_bonds = QPushButton("erase selected bonds")
        erase_bonds.clicked.connect(
            lambda *, ses=self.session: run(ses, "delete bonds sel"))
        bond_options.addRow(erase_bonds)
        self.erase_bonds = erase_bonds

        hbond_tab = QWidget()
        hbond_options = QFormLayout(hbond_tab)

        self.hbond_color = ColorButton(has_alpha_channel=True,
                                       max_size=(16, 16))
        self.hbond_color.set_color(self.settings.hbond_color)
        hbond_options.addRow("color:", self.hbond_color)

        self.hbond_radius = QDoubleSpinBox()
        self.hbond_radius.setDecimals(3)
        self.hbond_radius.setSuffix(" \u212B")
        self.hbond_radius.setValue(self.settings.hbond_radius)
        hbond_options.addRow("radius:", self.hbond_radius)

        self.hbond_dashes = QSpinBox()
        self.hbond_dashes.setRange(0, 28)
        self.hbond_dashes.setSingleStep(2)
        self.hbond_radius.setSingleStep(0.005)
        self.hbond_dashes.setValue(self.settings.hbond_dashes)
        hbond_options.addRow("dashes:", self.hbond_dashes)

        draw_hbonds = QPushButton("draw H-bonds")
        draw_hbonds.clicked.connect(self.run_hbond)
        hbond_options.addRow(draw_hbonds)
        self.draw_hbonds = draw_hbonds

        erase_hbonds = QPushButton("erase all H-bonds")
        erase_hbonds.clicked.connect(
            lambda *, ses=self.session: run(ses, "~hbonds"))
        hbond_options.addRow(erase_hbonds)
        self.erase_hbonds = erase_hbonds

        tm_bond_tab = QWidget()
        tm_bond_options = QFormLayout(tm_bond_tab)

        self.tm_bond_color = ColorButton(has_alpha_channel=True,
                                         max_size=(16, 16))
        self.tm_bond_color.set_color(self.settings.tm_bond_color)
        tm_bond_options.addRow("color:", self.tm_bond_color)

        self.tm_bond_radius = QDoubleSpinBox()
        self.tm_bond_radius.setDecimals(3)
        self.tm_bond_radius.setSuffix(" \u212B")
        self.tm_bond_radius.setValue(self.settings.tm_bond_radius)
        tm_bond_options.addRow("radius:", self.tm_bond_radius)

        self.tm_bond_dashes = QSpinBox()
        self.tm_bond_dashes.setRange(0, 28)
        self.tm_bond_dashes.setSingleStep(2)
        self.tm_bond_radius.setSingleStep(0.005)
        self.tm_bond_dashes.setValue(self.settings.tm_bond_dashes)
        tm_bond_options.addRow("dashes:", self.tm_bond_dashes)

        draw_tm_bonds = QPushButton("draw metal coordination bonds")
        draw_tm_bonds.clicked.connect(self.run_tm_bond)
        tm_bond_options.addRow(draw_tm_bonds)
        self.draw_tm_bonds = draw_tm_bonds

        erase_tm_bonds = QPushButton("erase all metal coordination bonds")
        erase_tm_bonds.clicked.connect(self.del_tm_bond)
        tm_bond_options.addRow(erase_tm_bonds)
        self.erase_tm_bonds = erase_tm_bonds

        bond_length_tab = QWidget()
        bond_length_layout = QFormLayout(bond_length_tab)

        self.bond_distance = QDoubleSpinBox()
        self.bond_distance.setRange(0.5, 10.0)
        self.bond_distance.setSingleStep(0.05)
        self.bond_distance.setValue(1.51)
        self.bond_distance.setSuffix(" \u212B")
        bond_length_layout.addRow("bond length:", self.bond_distance)

        self.move_fragment = QComboBox()
        self.move_fragment.addItems(["both", "smaller", "larger"])
        bond_length_layout.addRow("move side:", self.move_fragment)

        bond_lookup = QGroupBox("bond length lookup:")
        bond_lookup_layout = QGridLayout(bond_lookup)

        bond_lookup_layout.addWidget(
            QLabel("elements:"),
            0,
            0,
        )

        self.ele1 = ElementButton("C", single_state=True)
        self.ele1.clicked.connect(
            lambda *args, button=self.ele1: self.open_ptable(button))
        bond_lookup_layout.addWidget(self.ele1, 0, 1,
                                     Qt.AlignRight | Qt.AlignTop)

        bond_lookup_layout.addWidget(QLabel("-"), 0, 2,
                                     Qt.AlignHCenter | Qt.AlignVCenter)

        self.ele2 = ElementButton("C", single_state=True)
        self.ele2.clicked.connect(
            lambda *args, button=self.ele2: self.open_ptable(button))
        bond_lookup_layout.addWidget(self.ele2, 0, 3,
                                     Qt.AlignLeft | Qt.AlignTop)

        bond_lookup_layout.addWidget(QLabel("bond order:"), 1, 0)

        self.bond_order = BondOrderSpinBox()
        self.bond_order.setRange(1., 3.)
        self.bond_order.setValue(1)
        self.bond_order.setSingleStep(0.5)
        self.bond_order.setDecimals(1)
        self.bond_order.valueChanged.connect(self.check_bond_lengths)
        bond_lookup_layout.addWidget(self.bond_order, 1, 1, 1, 3)

        bond_lookup_layout.setColumnStretch(0, 0)
        bond_lookup_layout.setColumnStretch(1, 0)
        bond_lookup_layout.setColumnStretch(2, 0)
        bond_lookup_layout.setColumnStretch(3, 1)

        bond_length_layout.addRow(bond_lookup)

        self.status = QStatusBar()
        self.status.setSizeGripEnabled(False)
        bond_lookup_layout.addWidget(self.status, 2, 0, 1, 4)

        self.do_bond_change = QPushButton("change selected bond lengths")
        self.do_bond_change.clicked.connect(self.change_bond_length)
        bond_length_layout.addRow(self.do_bond_change)

        tabs.addTab(bond_tab, "covalent bonds")
        tabs.addTab(ts_bond_tab, "TS bonds")
        tabs.addTab(hbond_tab, "H-bonds")
        tabs.addTab(tm_bond_tab, "coordination bonds")
        tabs.addTab(bond_length_tab, "bond length")

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)

    def run_tsbond(self, *args):
        args = ["tsbond", "sel"]

        color = self.tsbond_color.get_color()
        args.extend(["color", "rgb(%i, %i, %i)" % tuple(color[:-1])])
        self.settings.tsbond_color = tuple([c / 255. for c in color])

        radius = self.tsbond_radius.value()
        args.extend(["radius", "%.3f" % radius])
        self.settings.tsbond_radius = radius

        transparency = self.tsbond_transparency.value()
        args.extend(["transparency", "%i" % transparency])
        self.settings.tsbond_transparency = transparency

        run(self.session, " ".join(args))

    def run_erase_tsbond(self, *args):
        run(self.session, "~tsbond sel")

    def run_bond(self, *args):
        # TODO: switch to `bond sel` in 1.2
        sel = selected_atoms(self.session)
        halfbond = self.bond_halfbond.checkState() == Qt.Checked
        self.settings.bond_halfbond = halfbond

        if not halfbond:
            color = self.bond_color.get_color()
            color = tuple(x / 255. for x in color)
            self.settings.bond_color = color

        radius = self.bond_radius.value()
        self.settings.bond_radius = radius

        for b in selected_bonds(self.session):
            b.halfbond = halfbond
            if not halfbond:
                b.color = np.array([int(x * 255) for x in color])

            b.radius = radius

        for i, a1 in enumerate(sel):
            for a2 in sel[:i]:
                if a1.structure is a2.structure and a2 not in a1.neighbors:
                    new_bond = a1.structure.new_bond(a1, a2)
                    new_bond.halfbond = halfbond

                    if not halfbond:
                        new_bond.color = np.array(
                            [int(x * 255) for x in color])

                    new_bond.radius = radius

    def run_hbond(self, *args):
        args = ["hbonds", "reveal", "true"]

        color = self.hbond_color.get_color()
        args.extend(["color", "rgb(%i, %i, %i)" % tuple(color[:-1])])
        self.settings.hbond_color = tuple([c / 255. for c in color])

        radius = self.hbond_radius.value()
        args.extend(["radius", "%.3f" % radius])
        self.settings.hbond_radius = radius

        dashes = self.hbond_dashes.value()
        args.extend(["dashes", "%i" % dashes])
        self.settings.hbond_dashes = dashes

        run(self.session, " ".join(args))

    def run_tm_bond(self, *args):
        color = self.tm_bond_color.get_color()
        self.settings.tm_bond_color = tuple([c / 255. for c in color])

        radius = self.tm_bond_radius.value()
        self.settings.tm_bond_radius = radius

        dashes = self.tm_bond_dashes.value()
        self.settings.tm_bond_dashes = dashes

        models = self.session.models.list(type=AtomicStructure)
        for model in models:
            rescol = ResidueCollection(model, bonds_matter=False)
            try:
                tm_list = rescol.find([
                    AnyTransitionMetal(), "Na", "K", "Rb", "Cs", "Fr", "Mg",
                    "Ca", "Sr", "Ba", "Ra"
                ])
                for tm in tm_list:
                    for atom in rescol.atoms:
                        if atom is tm:
                            continue

                        if atom.is_connected(tm):
                            pbg = model.pseudobond_group(
                                model.PBG_METAL_COORDINATION,
                                create_type="normal")
                            pbg.new_pseudobond(tm.chix_atom, atom.chix_atom)
                            pbg.dashes = dashes
                            pbg.color = color
                            pbg.radius = radius

            except LookupError:
                pass

    def del_tm_bond(self, *args):
        models = self.session.models.list(type=AtomicStructure)
        for model in models:
            pbg = model.pseudobond_group(model.PBG_METAL_COORDINATION,
                                         create_type=None)
            if pbg is not None:
                pbg.delete()

    def open_ptable(self, button):
        self.tool_window.create_child_window("select element",
                                             window_class=PTable2,
                                             button=button,
                                             callback=self.check_bond_lengths)

    def check_bond_lengths(self, *args):
        ele1 = self.ele1.text()
        ele2 = self.ele2.text()
        key = ORDER_BOND_ORDER.key(ele1, ele2)

        order = "%.1f" % self.bond_order.value()
        if key in ORDER_BOND_ORDER.bonds and order in ORDER_BOND_ORDER.bonds[
                key]:
            self.bond_distance.setValue(ORDER_BOND_ORDER.bonds[key][order])
            self.status.showMessage("")
        else:
            self.status.showMessage("no bond data for %s-%s %sx bonds" %
                                    (ele1, ele2, order))

    def change_bond_length(self, *args):
        dist = self.bond_distance.value()

        atom_pairs = []

        sel = selected_atoms(self.session)
        if len(sel) == 2 and sel[0].structure is sel[1].structure:
            atom_pairs.append(sel)

        for bond in selected_bonds(self.session):
            if not all(atom in sel for atom in bond.atoms):
                atom_pairs.append(bond.atoms)

        for bond in selected_pseudobonds(self.session):
            if not all(atom in sel for atom in bond.atoms):
                atom_pairs.append(bond.atoms)

        for pair in atom_pairs:
            atom1, atom2 = pair
            frag1 = get_fragment(atom1,
                                 stop=atom2,
                                 max_len=atom1.structure.num_atoms)
            frag2 = get_fragment(atom2,
                                 stop=atom1,
                                 max_len=atom1.structure.num_atoms)

            v = atom2.coord - atom1.coord

            cur_dist = np.linalg.norm(v)
            change = dist - cur_dist

            if self.move_fragment.currentText() == "both":
                change = 0.5 * change
                frag1.coords -= change * v / cur_dist
                frag2.coords += change * v / cur_dist
            elif self.move_fragment.currentText() == "smaller":
                if len(frag1) < len(frag2) or (len(frag1) == len(frag2)
                                               and sum(frag1.elements.masses) <
                                               sum(frag2.elements.masses)):
                    frag1.coords -= change * v / cur_dist
                else:
                    frag2.coords += change * v / cur_dist
            elif self.move_fragment.currentText() == "larger":
                if len(frag1) > len(frag2) or (len(frag1) == len(frag2)
                                               and sum(frag1.elements.masses) >
                                               sum(frag2.elements.masses)):
                    frag1.coords -= change * v / cur_dist
                else:
                    frag2.coords += change * v / cur_dist
예제 #7
0
class LigandSterimol(ToolInstance):

    help = "https://github.com/QChASM/SEQCROW/wiki/Ligand-Sterimol-Tool"
    SESSION_ENDURING = False
    SESSION_SAVE = False

    def __init__(self, session, name):
        super().__init__(session, name)

        from chimerax.ui import MainToolWindow
        self.tool_window = MainToolWindow(self)

        self.settings = _SterimolSettings(self.session, name)

        self._build_ui()

    def _build_ui(self):
        layout = QFormLayout()

        self.radii_option = QComboBox()
        self.radii_option.addItems(["Bondi", "UMN"])
        ndx = self.radii_option.findText(self.settings.radii, Qt.MatchExactly)
        self.radii_option.setCurrentIndex(ndx)
        layout.addRow("radii:", self.radii_option)

        self.L_option = QComboBox()
        self.L_option.addItems([
            "to centroid of coordinating atoms",
            "bisect angle between coordinating atoms"
        ])
        ndx = self.L_option.findText(self.settings.L_option, Qt.MatchExactly)
        self.L_option.setCurrentIndex(ndx)
        layout.addRow("L axis:", self.L_option)

        self.sterimol2vec = QGroupBox("Sterimol2Vec")
        sterimol2vec_layout = QFormLayout(self.sterimol2vec)

        self.at_L = QDoubleSpinBox()
        self.at_L.setRange(-10, 30)
        self.at_L.setDecimals(2)
        self.at_L.setSingleStep(0.25)
        self.at_L.setValue(self.settings.at_L)
        sterimol2vec_layout.addRow("L value:", self.at_L)

        layout.addRow(self.sterimol2vec)

        self.sterimol2vec.setCheckable(True)
        self.sterimol2vec.toggled.connect(lambda x: self.at_L.setEnabled(x))
        self.sterimol2vec.setChecked(self.settings.sterimol2vec)

        self.display_vectors = QCheckBox()
        self.display_vectors.setChecked(self.settings.display_vectors)
        layout.addRow("show vectors:", self.display_vectors)

        self.display_radii = QCheckBox()
        self.display_radii.setChecked(self.settings.display_radii)
        layout.addRow("show radii:", self.display_radii)

        calc_sterimol_button = QPushButton(
            "calculate parameters for selected ligands")
        calc_sterimol_button.clicked.connect(self.calc_sterimol)
        layout.addRow(calc_sterimol_button)
        self.calc_sterimol_button = calc_sterimol_button

        remove_sterimol_button = QPushButton("remove Sterimol visualizations")
        remove_sterimol_button.clicked.connect(self.del_sterimol)
        layout.addRow(remove_sterimol_button)
        self.remove_sterimol_button = remove_sterimol_button

        self.table = QTableWidget()
        self.table.setColumnCount(8)
        self.table.setHorizontalHeaderLabels([
            'model',
            'coord. atoms',
            'B\u2081',
            'B\u2082',
            'B\u2083',
            'B\u2084',
            'B\u2085',
            'L',
        ])
        self.table.setSelectionBehavior(QTableWidget.SelectRows)
        self.table.setEditTriggers(QTableWidget.NoEditTriggers)
        self.table.resizeColumnToContents(0)
        self.table.resizeColumnToContents(1)
        self.table.resizeColumnToContents(2)
        self.table.resizeColumnToContents(3)
        self.table.resizeColumnToContents(4)
        self.table.resizeColumnToContents(5)
        self.table.resizeColumnToContents(6)
        self.table.resizeColumnToContents(7)
        self.table.horizontalHeader().setSectionResizeMode(
            2, QHeaderView.Stretch)
        self.table.horizontalHeader().setSectionResizeMode(
            3, QHeaderView.Stretch)
        self.table.horizontalHeader().setSectionResizeMode(
            4, QHeaderView.Stretch)
        self.table.horizontalHeader().setSectionResizeMode(
            5, QHeaderView.Stretch)
        self.table.horizontalHeader().setSectionResizeMode(
            6, QHeaderView.Stretch)
        self.table.horizontalHeader().setSectionResizeMode(
            7, QHeaderView.Stretch)
        layout.addRow(self.table)

        menu = QMenuBar()

        export = menu.addMenu("&Export")

        clear = QAction("Clear data table", self.tool_window.ui_area)
        clear.triggered.connect(self.clear_table)
        export.addAction(clear)

        copy = QAction("&Copy CSV to clipboard", self.tool_window.ui_area)
        copy.triggered.connect(self.copy_csv)
        shortcut = QKeySequence(Qt.CTRL + Qt.Key_C)
        copy.setShortcut(shortcut)
        export.addAction(copy)

        save = QAction("&Save CSV...", self.tool_window.ui_area)
        save.triggered.connect(self.save_csv)
        #this shortcut interferes with main window's save shortcut
        #I've tried different shortcut contexts to no avail
        #thanks Qt...
        #shortcut = QKeySequence(Qt.CTRL + Qt.Key_S)
        #save.setShortcut(shortcut)
        #save.setShortcutContext(Qt.WidgetShortcut)
        export.addAction(save)

        delimiter = export.addMenu("Delimiter")

        comma = QAction("comma", self.tool_window.ui_area, checkable=True)
        comma.setChecked(self.settings.delimiter == "comma")
        comma.triggered.connect(lambda *args, delim="comma": self.settings.
                                __setattr__("delimiter", delim))
        delimiter.addAction(comma)

        tab = QAction("tab", self.tool_window.ui_area, checkable=True)
        tab.setChecked(self.settings.delimiter == "tab")
        tab.triggered.connect(lambda *args, delim="tab": self.settings.
                              __setattr__("delimiter", delim))
        delimiter.addAction(tab)

        space = QAction("space", self.tool_window.ui_area, checkable=True)
        space.setChecked(self.settings.delimiter == "space")
        space.triggered.connect(lambda *args, delim="space": self.settings.
                                __setattr__("delimiter", delim))
        delimiter.addAction(space)

        semicolon = QAction("semicolon",
                            self.tool_window.ui_area,
                            checkable=True)
        semicolon.setChecked(self.settings.delimiter == "semicolon")
        semicolon.triggered.connect(lambda *args, delim="semicolon": self.
                                    settings.__setattr__("delimiter", delim))
        delimiter.addAction(semicolon)

        add_header = QAction("&Include CSV header",
                             self.tool_window.ui_area,
                             checkable=True)
        add_header.setChecked(self.settings.include_header)
        add_header.triggered.connect(self.header_check)
        export.addAction(add_header)

        comma.triggered.connect(
            lambda *args, action=tab: action.setChecked(False))
        comma.triggered.connect(
            lambda *args, action=space: action.setChecked(False))
        comma.triggered.connect(
            lambda *args, action=semicolon: action.setChecked(False))

        tab.triggered.connect(
            lambda *args, action=comma: action.setChecked(False))
        tab.triggered.connect(
            lambda *args, action=space: action.setChecked(False))
        tab.triggered.connect(
            lambda *args, action=semicolon: action.setChecked(False))

        space.triggered.connect(
            lambda *args, action=comma: action.setChecked(False))
        space.triggered.connect(
            lambda *args, action=tab: action.setChecked(False))
        space.triggered.connect(
            lambda *args, action=semicolon: action.setChecked(False))

        semicolon.triggered.connect(
            lambda *args, action=comma: action.setChecked(False))
        semicolon.triggered.connect(
            lambda *args, action=tab: action.setChecked(False))
        semicolon.triggered.connect(
            lambda *args, action=space: action.setChecked(False))

        menu.setNativeMenuBar(False)
        self._menu = menu
        layout.setMenuBar(menu)
        menu.setVisible(True)

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)

    def clear_table(self):
        are_you_sure = QMessageBox.question(
            None,
            "Clear table?",
            "Are you sure you want to clear the data table?",
        )
        if are_you_sure != QMessageBox.Yes:
            return
        self.table.setRowCount(0)

    def calc_sterimol(self, *args):
        self.settings.radii = self.radii_option.currentText()
        self.settings.display_radii = self.display_radii.checkState(
        ) == Qt.Checked
        self.settings.display_vectors = self.display_vectors.checkState(
        ) == Qt.Checked
        self.settings.at_L = self.at_L.value()
        self.settings.sterimol2vec = self.sterimol2vec.isChecked()
        self.settings.L_option = self.L_option.currentText()

        targets, neighbors, datas = sterimol_cmd(
            self.session,
            selected_atoms(self.session),
            radii=self.radii_option.currentText(),
            showVectors=self.display_vectors.checkState() == Qt.Checked,
            showRadii=self.display_radii.checkState() == Qt.Checked,
            return_values=True,
            at_L=self.at_L.value() if self.sterimol2vec.isChecked() else None,
            bisect_L=self.L_option.currentText() ==
            "bisect angle between coordinating atoms",
        )

        if len(targets) == 0:
            return

        if self.settings.delimiter == "comma":
            delim = ","
        elif self.settings.delimiter == "space":
            delim = " "
        elif self.settings.delimiter == "tab":
            delim = "\t"
        elif self.settings.delimiter == "semicolon":
            delim = ";"

        # self.table.setRowCount(0)

        for t, b, data in zip(targets, neighbors, datas):
            row = self.table.rowCount()
            self.table.insertRow(row)

            targ = QTableWidgetItem()
            targ.setData(Qt.DisplayRole, t)
            self.table.setItem(row, 0, targ)

            neigh = QTableWidgetItem()
            neigh.setData(Qt.DisplayRole, delim.join(b))
            self.table.setItem(row, 1, neigh)

            l = np.linalg.norm(data["L"][1] - data["L"][0])
            b1 = np.linalg.norm(data["B1"][1] - data["B1"][0])
            b2 = np.linalg.norm(data["B2"][1] - data["B2"][0])
            b3 = np.linalg.norm(data["B3"][1] - data["B3"][0])
            b4 = np.linalg.norm(data["B4"][1] - data["B4"][0])
            b5 = np.linalg.norm(data["B5"][1] - data["B5"][0])

            li = QTableWidgetItem()
            li.setData(Qt.DisplayRole, "%.2f" % l)
            self.table.setItem(row, 7, li)

            b1i = QTableWidgetItem()
            b1i.setData(Qt.DisplayRole, "%.2f" % b1)
            self.table.setItem(row, 2, b1i)

            b2i = QTableWidgetItem()
            b2i.setData(Qt.DisplayRole, "%.2f" % b2)
            self.table.setItem(row, 3, b2i)

            b3i = QTableWidgetItem()
            b3i.setData(Qt.DisplayRole, "%.2f" % b3)
            self.table.setItem(row, 4, b3i)

            b4i = QTableWidgetItem()
            b4i.setData(Qt.DisplayRole, "%.2f" % b4)
            self.table.setItem(row, 5, b4i)

            b5i = QTableWidgetItem()
            b5i.setData(Qt.DisplayRole, "%.2f" % b5)
            self.table.setItem(row, 6, b5i)

        for i in range(0, 7):
            if i == 1:
                continue
            self.table.resizeColumnToContents(i)

    def header_check(self, state):
        """user has [un]checked the 'include header' option on the menu"""
        if state:
            self.settings.include_header = True
        else:
            self.settings.include_header = False

    def get_csv(self):
        if self.settings.delimiter == "comma":
            delim = ","
        elif self.settings.delimiter == "space":
            delim = " "
        elif self.settings.delimiter == "tab":
            delim = "\t"
        elif self.settings.delimiter == "semicolon":
            delim = ";"

        if self.settings.include_header:
            s = delim.join([
                "substituent_atom", "bonded_atom", "B1", "B2", "B3", "B4",
                "B5", "L"
            ])
            s += "\n"
        else:
            s = ""

        for i in range(0, self.table.rowCount()):
            s += delim.join([
                item.data(Qt.DisplayRole)
                for item in [self.table.item(i, j) for j in range(0, 8)]
            ])
            s += "\n"

        return s

    def copy_csv(self):
        app = QApplication.instance()
        clipboard = app.clipboard()
        csv = self.get_csv()
        clipboard.setText(csv)
        self.session.logger.status("copied to clipboard")

    def save_csv(self):
        """save data on current tab to CSV file"""
        filename, _ = QFileDialog.getSaveFileName(filter="CSV Files (*.csv)")
        if filename:
            s = self.get_csv()

            with open(filename, 'w') as f:
                f.write(s.strip())

            self.session.logger.status("saved to %s" % filename)

    def del_sterimol(self):
        for model in self.session.models.list(type=Generic3DModel):
            if model.name.startswith("Sterimol"):
                model.delete()

    def display_help(self):
        """Show the help for this tool in the help viewer."""
        from chimerax.core.commands import run
        run(self.session,
            'open %s' % self.help if self.help is not None else "")
예제 #8
0
class _ElementPicker(ToolInstance):
    def __init__(self, *args):
        super().__init__(*args)

        self.tool_window = MainToolWindow(self)
        self._build_ui()

    def _build_ui(self):
        layout = QFormLayout()

        initial_elements = []
        if ChangeElementMouseMode.element:
            initial_elements = [ChangeElementMouseMode.element]
        else:
            initial_elements = ["C"]

        self.periodic_table = PeriodicTable(select_multiple=False,
                                            initial_elements=initial_elements)
        self.periodic_table.elementSelectionChanged.connect(
            self.element_changed)
        layout.addRow(self.periodic_table)

        self.vsepr = QComboBox()
        self.vsepr.addItems([
            "do not change",  # 0
            "linear (1 bond)",  # 1
            "linear (2 bonds)",  # 2 
            "trigonal planar (2 bonds)",  # 3
            "tetrahedral (2 bonds)",  # 4 
            "trigonal planar",  # 5
            "tetrahedral (3 bonds)",  # 6
            "T-shaped",  # 7
            "trigonal pyramidal",  # 8
            "tetrahedral",  # 9
            "sawhorse",  #10
            "seesaw",  #11
            "square planar",  #12
            "trigonal bipyramidal",  #13
            "square pyramidal",  #14
            "pentagonal",  #15
            "octahedral",  #16
            "hexagonal",  #17
            "trigonal prismatic",  #18
            "pentagonal pyramidal",  #19
            "capped octahedral",  #20
            "capped trigonal prismatic",  #21
            "heptagonal",  #22
            "hexagonal pyramidal",  #23
            "pentagonal bipyramidal",  #24
            "biaugmented trigonal prismatic",  #25
            "cubic",  #26
            "elongated trigonal bipyramidal",  #27
            "hexagonal bipyramidal",  #28
            "heptagonal pyramidal",  #29
            "octagonal",  #30
            "square antiprismatic",  #31
            "trigonal dodecahedral",  #32
            "capped cube",  #33
            "capped square antiprismatic",  #34
            "enneagonal",  #35
            "heptagonal bipyramidal",  #36
            "hula-hoop",  #37
            "triangular cupola",  #38
            "tridiminished icosahedral",  #39
            "muffin",  #40
            "octagonal pyramidal",  #41
            "tricapped trigonal prismatic",  #42
        ])
        self.vsepr.setCurrentIndex(9)

        self.vsepr.insertSeparator(33)
        self.vsepr.insertSeparator(25)
        self.vsepr.insertSeparator(20)
        self.vsepr.insertSeparator(16)
        self.vsepr.insertSeparator(13)
        self.vsepr.insertSeparator(8)
        self.vsepr.insertSeparator(5)
        self.vsepr.insertSeparator(2)
        self.vsepr.insertSeparator(1)
        self.vsepr.insertSeparator(0)
        self.vsepr.currentTextChanged.connect(self.element_changed)
        layout.addRow("shape:", self.vsepr)

        self.keep_open = QCheckBox()
        layout.addRow("keep table open:", self.keep_open)

        if ChangeElementMouseMode.vsepr:
            ndx = self.vsepr.findText(ChangeElementMouseMode.vsepr,
                                      Qt.MatchExactly)
            if ndx != -1:
                self.vsepr.setCurrentIndex(ndx)

        do_it = QPushButton("set element and shape")
        do_it.clicked.connect(self.set_selected)
        layout.addRow(do_it)

        self.keep_open.stateChanged.connect(
            lambda state: do_it.setVisible(state != Qt.Checked))
        self.keep_open.stateChanged.connect(self.element_changed)

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)

    def element_changed(self):
        if self.keep_open.checkState() != Qt.Checked:
            return
        element = self.periodic_table.selectedElements()
        if element:
            ChangeElementMouseMode.element = element[0]
            ChangeElementMouseMode.vsepr = self.vsepr.currentText()
            self.session.logger.status(
                "change element mouse mode set to use %s %s" %
                (ChangeElementMouseMode.vsepr, ChangeElementMouseMode.element))

    def set_selected(self, *args):
        element = self.periodic_table.selectedElements()
        if element:
            ChangeElementMouseMode.element = element[0]
            ChangeElementMouseMode.vsepr = self.vsepr.currentText()
            self.session.logger.status(
                "change element mouse mode set to use %s %s" %
                (ChangeElementMouseMode.vsepr, ChangeElementMouseMode.element))

        if self.keep_open.checkState() != Qt.Checked:
            self.delete()
예제 #9
0
class AARONInputBuilder(ToolInstance):
    SESSION_ENDURING = False
    SESSION_SAVE = False

    def __init__(self, session, name):
        super().__init__(session, name)

        self.theory_settings = _InputGeneratorSettings(session,
                                                       "Build QM Input",
                                                       version="3")

        self.tool_window = MainToolWindow(self)

        self._build_ui()

    def _build_ui(self):
        layout = QGridLayout()

        tab_widget = QTabWidget()
        layout.addWidget(tab_widget)

        structure_widget = QWidget()
        structure_layout = QGridLayout(structure_widget)
        tab_widget.addTab(structure_widget, "structure")

        self.structure_source = QComboBox()
        self.structure_source.addItems(
            ["AARON template", "file", "coordination complexes", "SMILES"])

        structure_layout.addWidget(QLabel("structure source:"), 0, 0, 1, 1,
                                   Qt.AlignLeft | Qt.AlignVCenter)

        structure_layout.addWidget(
            self.structure_source,
            0,
            1,
            1,
            1,
        )

        open_button = QPushButton("open")
        open_button.clicked.connect(self.open_template)
        structure_layout.addWidget(
            open_button,
            0,
            2,
            1,
            1,
        )

        # SMILES options
        self.smiles_options = QGroupBox("SMILES options")
        smiles_layout = QFormLayout(self.smiles_options)
        structure_layout.addWidget(self.smiles_options, 1, 0, 1, 3,
                                   Qt.AlignTop | Qt.AlignHCenter)

        self.smiles_line = QLineEdit()
        smiles_layout.addRow("SMILES string:", self.smiles_line)

        self.smiles_options.setVisible(
            self.structure_source.currentText() == "SMILES")
        self.structure_source.currentTextChanged.connect(
            lambda text: self.smiles_options.setVisible(text == "SMILES"))

        # coordination complexes options
        self.coord_comp_options = QGroupBox("Coordination Complex Options")
        coord_layout = QFormLayout(self.coord_comp_options)
        structure_layout.addWidget(self.coord_comp_options, 1, 0, 1, 3,
                                   Qt.AlignTop | Qt.AlignHCenter)

        create_coord_items(self,
                           coord_layout,
                           allow_minimization=False,
                           default_ele="Pd")

        self.coord_comp_options.setVisible(
            self.structure_source.currentText() == "coordination complexes")
        self.structure_source.currentTextChanged.connect(
            lambda text: self.coord_comp_options.setVisible(
                text == "coordination complexes"))

        # aaron template options
        self.aaron_options = QGroupBox("AARON template structures")
        aaron_layout = QFormLayout(self.aaron_options)
        structure_layout.addWidget(self.aaron_options, 1, 0, 1, 3,
                                   Qt.AlignTop | Qt.AlignHCenter)

        self.aaron_options.setVisible(
            self.structure_source.currentText() == "AARON template")
        self.structure_source.currentTextChanged.connect(
            lambda text: self.aaron_options.setVisible(text == "AARON template"
                                                       ))

        # file options
        self.file_options = QGroupBox("single file options")
        file_layout = QFormLayout(self.file_options)
        structure_layout.addWidget(self.file_options, 1, 0, 1, 3,
                                   Qt.AlignTop | Qt.AlignHCenter)

        file_widget = QWidget()
        file_browse_layout = QGridLayout(file_widget)

        self.template_file = QLineEdit()
        file_browse_layout.addWidget(self.template_file, 0, 0, 1, 1,
                                     Qt.AlignCenter)
        browse_button = QPushButton("browse...")
        # browse_button.clicked.connect(self.browse_file_template)
        file_browse_layout.addWidget(browse_button, 0, 1, 1, 1, Qt.AlignCenter)

        file_browse_layout.setColumnStretch(0, 1)
        file_browse_layout.setColumnStretch(1, 0)
        margins = file_browse_layout.contentsMargins()
        file_browse_layout.setContentsMargins(margins.left(), 0,
                                              margins.right(), 0)

        file_layout.addRow("file:", file_widget)

        self.file_options.setVisible(
            self.structure_source.currentText() == "file")
        self.structure_source.currentTextChanged.connect(
            lambda text: self.file_options.setVisible(text == "file"))

        self.optimize_template = QCheckBox()
        self.optimize_template.setCheckState(Qt.Checked)
        structure_layout.addWidget(
            QLabel("optimize template:"),
            2,
            0,
            1,
            1,
            Qt.AlignLeft | Qt.AlignVCenter,
        )
        structure_layout.addWidget(
            self.optimize_template,
            2,
            1,
            1,
            2,
            Qt.AlignLeft | Qt.AlignVCenter,
        )

        structure_layout.setColumnStretch(0, 0)
        structure_layout.setColumnStretch(1, 1)
        structure_layout.setColumnStretch(2, 0)

        # structure changes
        changes_widget = QWidget()
        changes_layout = QGridLayout(changes_widget)
        tab_widget.addTab(changes_widget, "changes")

        # HPC settings
        hpc_widget = QWidget()
        hpc_layout = QGridLayout(hpc_widget)
        tab_widget.addTab(hpc_widget, "HPC")

        # theory settings
        theory_widget = QWidget()
        theory_layout = QGridLayout(theory_widget)
        tab_widget.addTab(theory_widget, "theory")

        # results
        results_widget = QWidget()
        results_layout = QGridLayout(results_widget)
        tab_widget.addTab(results_widget, "results")

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)

    def open_template(self):
        pass
예제 #10
0
class PrecisionRotate(ToolInstance):

    help = "https://github.com/QChASM/SEQCROW/wiki/Rotate-Tool"
    SESSION_ENDURING = False
    SESSION_SAVE = False

    def __init__(self, session, name):
        super().__init__(session, name)

        self.tool_window = MainToolWindow(self)

        self.settings = _PrecisionRotateSettings(session, name)

        self.bonds = {}
        self.bond_centers = {}
        self.groups = {}
        self.perpendiculars = {}
        self.perp_centers = {}
        self.manual_center = {}

        self._build_ui()

        self._show_rot_vec = self.session.triggers.add_handler(
            SELECTION_CHANGED, self.show_rot_vec)
        global_triggers = get_triggers()
        self._changes = global_triggers.add_handler("changes done",
                                                    self.show_rot_vec)

        self.show_rot_vec()

    def _build_ui(self):
        layout = QGridLayout()

        layout.addWidget(QLabel("center of rotation:"), 0, 0, 1, 1,
                         Qt.AlignLeft | Qt.AlignVCenter)

        self.cor_button = QComboBox()
        self.cor_button.addItems(
            ["automatic", "select atoms", "view's center of rotation"])
        layout.addWidget(self.cor_button, 0, 1, 1, 1, Qt.AlignTop)

        self.set_cor_selection = QPushButton("set selection")
        self.cor_button.currentTextChanged.connect(
            lambda t, widget=self.set_cor_selection: widget.setEnabled(
                t == "select atoms"))
        self.set_cor_selection.clicked.connect(self.manual_cor)
        layout.addWidget(self.set_cor_selection, 0, 2, 1, 1, Qt.AlignTop)
        self.set_cor_selection.setEnabled(False)

        layout.addWidget(QLabel("rotation vector:"), 1, 0, 1, 1,
                         Qt.AlignLeft | Qt.AlignVCenter)

        self.vector_option = QComboBox()
        self.vector_option.addItems([
            "axis", "view axis", "bond", "perpendicular to plane",
            "centroid of atoms", "custom"
        ])
        layout.addWidget(self.vector_option, 1, 1, 1, 1, Qt.AlignVCenter)

        vector = QWidget()
        vector.setToolTip("vector will be normalized before rotating")
        vector_layout = QHBoxLayout(vector)
        vector_layout.setContentsMargins(0, 0, 0, 0)
        self.vector_x = QDoubleSpinBox()
        self.vector_y = QDoubleSpinBox()
        self.vector_z = QDoubleSpinBox()
        self.vector_z.setValue(1.0)
        for c, t in zip([self.vector_x, self.vector_y, self.vector_z],
                        [" x", " y", " z"]):
            c.setSingleStep(0.01)
            c.setRange(-100, 100)
            # c.setSuffix(t)
            c.valueChanged.connect(self.show_rot_vec)
            vector_layout.addWidget(c)

        layout.addWidget(vector, 1, 2, 1, 1, Qt.AlignTop)
        vector.setVisible(self.vector_option.currentText() == "custom")
        self.vector_option.currentTextChanged.connect(
            lambda text, widget=vector: widget.setVisible(text == "custom"))

        self.view_axis = QComboBox()
        self.view_axis.addItems(["z", "y", "x"])
        layout.addWidget(self.view_axis, 1, 2, 1, 1, Qt.AlignTop)
        self.view_axis.setVisible(
            self.vector_option.currentText() == "view axis")
        self.vector_option.currentTextChanged.connect(
            lambda text, widget=self.view_axis: widget.setVisible(text ==
                                                                  "view axis"))

        self.axis = QComboBox()
        self.axis.addItems(["z", "y", "x"])
        layout.addWidget(self.axis, 1, 2, 1, 1, Qt.AlignTop)
        self.axis.setVisible(self.vector_option.currentText() == "axis")
        self.vector_option.currentTextChanged.connect(
            lambda text, widget=self.axis: widget.setVisible(text == "axis"))

        self.bond_button = QPushButton("set selected bond")
        self.bond_button.clicked.connect(self.set_bonds)
        layout.addWidget(self.bond_button, 1, 2, 1, 1, Qt.AlignTop)
        self.bond_button.setVisible(self.vector_option.currentText() == "bond")
        self.vector_option.currentTextChanged.connect(
            lambda text, widget=self.bond_button: widget.setVisible(text ==
                                                                    "bond"))

        self.perp_button = QPushButton("set selected atoms")
        self.perp_button.clicked.connect(self.set_perpendicular)
        layout.addWidget(self.perp_button, 1, 2, 1, 1, Qt.AlignTop)
        self.perp_button.setVisible(
            self.vector_option.currentText() == "perpendicular to plane")
        self.vector_option.currentTextChanged.connect(
            lambda text, widget=self.perp_button: widget.setVisible(
                text == "perpendicular to plane"))

        self.group_button = QPushButton("set selected atoms")
        self.group_button.clicked.connect(self.set_group)
        layout.addWidget(self.group_button, 1, 2, 1, 1, Qt.AlignTop)
        self.group_button.setVisible(
            self.vector_option.currentText() == "centroid of atoms")
        self.vector_option.currentTextChanged.connect(
            lambda text, widget=self.group_button: widget.setVisible(
                text == "centroid of atoms"))

        layout.addWidget(QLabel("angle:"), 2, 0, 1, 1,
                         Qt.AlignLeft | Qt.AlignVCenter)

        self.angle = QDoubleSpinBox()
        self.angle.setRange(-360, 360)
        self.angle.setSingleStep(5)
        self.angle.setSuffix("°")
        layout.addWidget(self.angle, 2, 1, 1, 1,
                         Qt.AlignLeft | Qt.AlignVCenter)

        layout.addWidget(QLabel("preview rotation axis:"), 3, 0, 1, 1,
                         Qt.AlignLeft | Qt.AlignVCenter)
        self.display_rot_vec = QCheckBox()
        self.display_rot_vec.setCheckState(Qt.Checked)
        self.display_rot_vec.stateChanged.connect(self.show_rot_vec)
        layout.addWidget(self.display_rot_vec, 3, 1, 1, 1,
                         Qt.AlignLeft | Qt.AlignVCenter)

        rotate_button = QPushButton("rotate selected atoms")
        rotate_button.clicked.connect(self.do_rotate)
        layout.addWidget(rotate_button, 4, 0, 1, 3, Qt.AlignTop)
        self.rotate_button = rotate_button

        self.status_bar = QStatusBar()
        self.status_bar.setSizeGripEnabled(False)
        layout.addWidget(self.status_bar, 5, 0, 1, 3, Qt.AlignTop)

        self.vector_option.currentTextChanged.connect(self.show_auto_status)
        self.cor_button.currentIndexChanged.connect(
            lambda *args: self.show_auto_status("select atoms"))

        self.cor_button.currentIndexChanged.connect(self.show_rot_vec)
        self.set_cor_selection.clicked.connect(self.show_rot_vec)
        self.vector_option.currentIndexChanged.connect(self.show_rot_vec)
        self.axis.currentIndexChanged.connect(self.show_rot_vec)
        self.view_axis.currentIndexChanged.connect(self.show_rot_vec)
        self.bond_button.clicked.connect(self.show_rot_vec)
        self.perp_button.clicked.connect(self.show_rot_vec)
        self.group_button.clicked.connect(self.show_rot_vec)

        layout.setRowStretch(0, 0)
        layout.setRowStretch(1, 0)
        layout.setRowStretch(2, 0)
        layout.setRowStretch(3, 0)
        layout.setRowStretch(4, 0)
        layout.setRowStretch(5, 1)

        layout.setColumnStretch(0, 0)
        layout.setColumnStretch(1, 1)
        layout.setColumnStretch(2, 1)

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)

    def manual_cor(self, *args):
        selection = selected_atoms(self.session)

        models = {}
        for atom in selection:
            if atom.structure not in models:
                models[atom.structure] = [atom]
            else:
                models[atom.structure].append(atom)

        self.manual_center = {}
        for model in models:
            atoms = models[model]
            coords = np.array([atom.coord for atom in atoms])
            self.manual_center[model] = np.mean(coords, axis=0)

    def show_auto_status(self, text):
        if self.cor_button.currentText() == "automatic":
            if text == "bond":
                self.status_bar.showMessage(
                    "center set to one of the bonded atoms")
            elif text == "perpendicular to plane":
                self.status_bar.showMessage("center set to centroid of atoms")
            else:
                self.status_bar.showMessage(
                    "center set to centroid of rotating atoms")

        elif self.cor_button.currentText() == "select atoms":
            self.status_bar.showMessage(
                "center set to centroid of specified atoms")

        else:
            self.status_bar.showMessage(
                "center set to view's center of rotation")

    def set_bonds(self, *args):
        bonds = selected_bonds(self.session)
        if len(bonds) == 0:
            self.session.logger.error("no bonds selected")
            return

        models = [bond.structure for bond in bonds]
        if any(models.count(m) > 1 for m in models):
            self.session.logger.error(
                "multiple bonds selected on the same structure")
            return

        self.bonds = {
            model: (bond.atoms[0].coord - bond.atoms[1].coord)
            for model, bond in zip(models, bonds)
        }
        self.bond_centers = {
            model: bond.atoms[1].coord
            for model, bond in zip(models, bonds)
        }

    def set_perpendicular(self, *args):
        atoms = selected_atoms(self.session)
        if len(atoms) == 0:
            self.session.logger.error("no atoms selected")
            return

        self.perpendiculars = {}
        self.perp_centers = {}

        models = set(atom.structure for atom in atoms)
        for model in models:
            atom_coords = []
            for atom in atoms:
                if atom.structure is model:
                    atom_coords.append(atom.coord)

            if len(atom_coords) < 3:
                self.session.logger.error("fewer than 3 atoms selected on %s" %
                                          model.atomspec)
                continue

            xyz = np.array(atom_coords)
            xyz -= np.mean(atom_coords, axis=0)
            R = np.dot(xyz.T, xyz)
            u, s, vh = np.linalg.svd(R, compute_uv=True)
            vector = u[:, -1]

            self.perpendiculars[model] = vector
            self.perp_centers[model] = np.mean(atom_coords, axis=0)

    def set_group(self, *args):
        atoms = selected_atoms(self.session)
        if len(atoms) == 0:
            self.session.logger.error("no atoms selected")
            return

        self.groups = {}

        models = set(atom.structure for atom in atoms)
        for model in models:
            atom_coords = []
            for atom in atoms:
                if atom.structure is model:
                    atom_coords.append(atom.coord)

            self.groups[model] = np.mean(atom_coords, axis=0)

    def do_rotate(self, *args):
        selection = selected_atoms(self.session)

        models = {}
        for atom in selection:
            if atom.structure not in models:
                models[atom.structure] = [atom]
            else:
                models[atom.structure].append(atom)

        if len(models.keys()) == 0:
            return

        if self.vector_option.currentText() == "axis":
            if self.axis.currentText() == "z":
                vector = np.array([0., 0., 1.])
            elif self.axis.currentText() == "y":
                vector = np.array([0., 1., 0.])
            elif self.axis.currentText() == "x":
                vector = np.array([1., 0., 0.])

        elif self.vector_option.currentText() == "view axis":
            if self.view_axis.currentText() == "z":
                vector = self.session.view.camera.get_position().axes()[2]
            elif self.view_axis.currentText() == "y":
                vector = self.session.view.camera.get_position().axes()[1]
            elif self.view_axis.currentText() == "x":
                vector = self.session.view.camera.get_position().axes()[0]

        elif self.vector_option.currentText() == "bond":
            vector = self.bonds

        elif self.vector_option.currentText() == "perpendicular to plane":
            vector = self.perpendiculars

        elif self.vector_option.currentText() == "centroid of atoms":
            vector = self.groups

        elif self.vector_option.currentText() == "custom":
            x = self.vector_x.value()
            y = self.vector_y.value()
            z = self.vector_z.value()
            vector = np.array([x, y, z])

        angle = np.deg2rad(self.angle.value())

        center = {}
        for model in models:
            atoms = models[model]
            coords = np.array([atom.coord for atom in atoms])
            center[model] = np.mean(coords, axis=0)

        if self.cor_button.currentText() == "automatic":
            if self.vector_option.currentText() == "perpendicular to plane":
                center = self.perp_centers

            elif self.vector_option.currentText() == "bond":
                center = self.bond_centers

        elif self.cor_button.currentText() == "select atoms":
            center = self.manual_center

        else:
            center = self.session.main_view.center_of_rotation

        for model in models:
            if isinstance(vector, dict):
                if model not in vector.keys():
                    continue
                else:
                    v = vector[model]

            else:
                v = vector

            if isinstance(center, dict):
                if model not in center.keys():
                    continue
                else:
                    c = center[model]

            else:
                c = center

            if self.vector_option.currentText(
            ) == "centroid of atoms" and self.cor_button.currentText(
            ) != "automatic":
                v = v - c

            v = v / np.linalg.norm(v)
            q = np.hstack(([np.cos(angle / 2)], v * np.sin(angle / 2)))

            q /= np.linalg.norm(q)
            qs = q[0]
            qv = q[1:]

            xyz = np.array([a.coord for a in models[model]])
            xyz -= c
            xprod = np.cross(qv, xyz)
            qs_xprod = 2 * qs * xprod
            qv_xprod = 2 * np.cross(qv, xprod)

            xyz += qs_xprod + qv_xprod + c
            for t, coord in zip(models[model], xyz):
                t.coord = coord

    def show_rot_vec(self, *args):
        for model in self.session.models.list(type=Generic3DModel):
            if model.name == "rotation vector":
                model.delete()

        if self.display_rot_vec.checkState() == Qt.Unchecked:
            return

        selection = selected_atoms(self.session)

        if len(selection) == 0:
            return

        models = {}
        for atom in selection:
            if atom.structure not in models:
                models[atom.structure] = [atom]
            else:
                models[atom.structure].append(atom)

        if len(models.keys()) == 0:
            return

        if self.vector_option.currentText() == "axis":
            if self.axis.currentText() == "z":
                vector = np.array([0., 0., 1.])
            elif self.axis.currentText() == "y":
                vector = np.array([0., 1., 0.])
            elif self.axis.currentText() == "x":
                vector = np.array([1., 0., 0.])

        elif self.vector_option.currentText() == "view axis":
            if self.view_axis.currentText() == "z":
                vector = self.session.view.camera.get_position().axes()[2]
            elif self.view_axis.currentText() == "y":
                vector = self.session.view.camera.get_position().axes()[1]
            elif self.view_axis.currentText() == "x":
                vector = self.session.view.camera.get_position().axes()[0]

        elif self.vector_option.currentText() == "bond":
            vector = self.bonds

        elif self.vector_option.currentText() == "perpendicular to plane":
            vector = self.perpendiculars

        elif self.vector_option.currentText() == "centroid of atoms":
            vector = self.groups

        elif self.vector_option.currentText() == "custom":
            x = self.vector_x.value()
            y = self.vector_y.value()
            z = self.vector_z.value()
            vector = np.array([x, y, z])

        center = {}
        for model in models:
            atoms = models[model]
            coords = np.array([atom.coord for atom in atoms])
            center[model] = np.mean(coords, axis=0)

        if self.cor_button.currentText() == "automatic":
            if self.vector_option.currentText() == "perpendicular to plane":
                center = self.perp_centers

            elif self.vector_option.currentText() == "bond":
                center = self.bond_centers

        elif self.cor_button.currentText() == "select atoms":
            center = self.manual_center

        else:
            center = self.session.main_view.center_of_rotation

        for model in models:
            if isinstance(vector, dict):
                if model not in vector.keys():
                    continue
                else:
                    v = vector[model]

            else:
                v = vector

            if isinstance(center, dict):
                if model not in center.keys():
                    continue
                else:
                    c = center[model]

            else:
                c = center

            if self.vector_option.currentText(
            ) == "centroid of atoms" and self.cor_button.currentText(
            ) != "automatic":
                v = v - c

            if np.linalg.norm(v) == 0:
                continue

            residues = []
            for atom in models[model]:
                if atom.residue not in residues:
                    residues.append(atom.residue)

            v_c = c + v

            s = ".color red\n"
            s += ".arrow %10.6f %10.6f %10.6f   %10.6f %10.6f %10.6f   0.2 0.4 0.7\n" % (
                *c, *v_c)

            stream = BytesIO(bytes(s, 'utf-8'))
            bild_obj, status = read_bild(self.session, stream,
                                         "rotation vector")

            self.session.models.add(bild_obj, parent=model)

    def delete(self):
        self.session.triggers.remove_handler(self._show_rot_vec)
        global_triggers = get_triggers()
        global_triggers.remove_handler(self._changes)

        for model in self.session.models.list(type=Generic3DModel):
            if model.name == "rotation vector":
                model.delete()

        return super().delete()

    def close(self):
        self.session.triggers.remove_handler(self._show_rot_vec)
        global_triggers = get_triggers()
        global_triggers.remove_handler(self._changes)

        for model in self.session.models.list(type=Generic3DModel):
            if model.name == "rotation vector":
                model.delete()

        return super().close()
예제 #11
0
class PercentVolumeBuried(ToolInstance):

    help = "https://github.com/QChASM/SEQCROW/wiki/Buried-Volume-Tool"
    SESSION_ENDURING = False
    SESSION_SAVE = False

    def __init__(self, session, name):
        super().__init__(session, name)

        self.tool_window = MainToolWindow(self)

        self.settings = _VburSettings(self.session, name)

        self.ligand_atoms = []

        self._build_ui()

    def _build_ui(self):
        layout = QGridLayout()

        tabs = QTabWidget()
        calc_widget = QWidget()
        calc_layout = QFormLayout(calc_widget)
        settings_widget = QWidget()
        settings_layout = QFormLayout(settings_widget)
        steric_map_widget = QWidget()
        steric_layout = QFormLayout(steric_map_widget)
        cutout_widget = QWidget()
        vol_cutout_layout = QFormLayout(cutout_widget)
        layout.addWidget(tabs)

        tabs.addTab(calc_widget, "calculation")
        tabs.addTab(settings_widget, "settings")
        tabs.addTab(steric_map_widget, "steric map")
        tabs.addTab(cutout_widget, "volume cutout")

        self.radii_option = QComboBox()
        self.radii_option.addItems(["Bondi", "UMN"])
        ndx = self.radii_option.findText(self.settings.radii, Qt.MatchExactly)
        self.radii_option.setCurrentIndex(ndx)
        settings_layout.addRow("radii:", self.radii_option)

        self.scale = QDoubleSpinBox()
        self.scale.setValue(self.settings.vdw_scale)
        self.scale.setSingleStep(0.01)
        self.scale.setRange(1., 1.5)
        settings_layout.addRow("VDW scale:", self.scale)

        set_ligand_atoms = QPushButton("set ligands to current selection")
        set_ligand_atoms.clicked.connect(self.set_ligand_atoms)
        set_ligand_atoms.setToolTip(
            "specify atoms to use in calculation\n" +
            "by default, all atoms will be used unless a single center is specified\n"
            +
            "in the case of a single center, all atoms except the center is used"
        )
        calc_layout.addRow(set_ligand_atoms)
        self.set_ligand_atoms = set_ligand_atoms

        self.radius = QDoubleSpinBox()
        self.radius.setValue(self.settings.center_radius)
        self.radius.setSuffix(" \u212B")
        self.radius.setDecimals(1)
        self.radius.setSingleStep(0.1)
        self.radius.setRange(1., 15.)
        settings_layout.addRow("radius around center:", self.radius)

        self.method = QComboBox()
        self.method.addItems(["Lebedev", "Monte-Carlo"])
        self.method.setToolTip("Lebedev: deterministic method\n" +
                               "Monte-Carlo: non-deterministic method")
        ndx = self.method.findText(self.settings.method, Qt.MatchExactly)
        self.method.setCurrentIndex(ndx)
        settings_layout.addRow("integration method:", self.method)

        leb_widget = QWidget()
        leb_layout = QFormLayout(leb_widget)
        leb_layout.setContentsMargins(0, 0, 0, 0)

        self.radial_points = QComboBox()
        self.radial_points.addItems(["20", "32", "64", "75", "99", "127"])
        self.radial_points.setToolTip(
            "more radial points will give more accurate results, but integration will take longer"
        )
        ndx = self.radial_points.findText(self.settings.radial_points,
                                          Qt.MatchExactly)
        self.radial_points.setCurrentIndex(ndx)
        leb_layout.addRow("radial points:", self.radial_points)

        self.angular_points = QComboBox()
        self.angular_points.addItems([
            "110", "194", "302", "590", "974", "1454", "2030", "2702", "5810"
        ])
        self.angular_points.setToolTip(
            "more angular points will give more accurate results, but integration will take longer"
        )
        ndx = self.angular_points.findText(self.settings.angular_points,
                                           Qt.MatchExactly)
        self.angular_points.setCurrentIndex(ndx)
        leb_layout.addRow("angular points:", self.angular_points)

        settings_layout.addRow(leb_widget)

        mc_widget = QWidget()
        mc_layout = QFormLayout(mc_widget)
        mc_layout.setContentsMargins(0, 0, 0, 0)

        self.min_iter = QSpinBox()
        self.min_iter.setValue(self.settings.minimum_iterations)
        self.min_iter.setRange(0, 10000)
        self.min_iter.setToolTip(
            "each iteration is 3000 points\n" +
            "iterations continue until convergence criteria are met")
        mc_layout.addRow("minimum interations:", self.min_iter)

        settings_layout.addRow(mc_widget)

        if self.settings.method == "Lebedev":
            mc_widget.setVisible(False)
        elif self.settings.method == "Monte-Carlo":
            leb_widget.setVisible(False)

        self.report_component = QComboBox()
        self.report_component.addItems(["total", "quadrants", "octants"])
        ndx = self.report_component.findText(self.settings.report_component,
                                             Qt.MatchExactly)
        self.report_component.setCurrentIndex(ndx)
        settings_layout.addRow("report volume:", self.report_component)

        self.use_scene = QCheckBox()
        self.use_scene.setChecked(self.settings.use_scene)
        self.use_scene.setToolTip(
            "quadrants/octants will use the orientation the molecule is displayed in"
        )
        settings_layout.addRow("use display orientation:", self.use_scene)

        self.method.currentTextChanged.connect(
            lambda text, widget=leb_widget: widget.setVisible(text == "Lebedev"
                                                              ))
        self.method.currentTextChanged.connect(
            lambda text, widget=mc_widget: widget.setVisible(text ==
                                                             "Monte-Carlo"))

        self.use_centroid = QCheckBox()
        self.use_centroid.setChecked(self.settings.use_centroid)
        self.use_centroid.setToolTip(
            "place the center between selected atoms\n" +
            "might be useful for polydentate ligands")
        calc_layout.addRow("use centroid of centers:", self.use_centroid)

        self.steric_map = QCheckBox()
        self.steric_map.setChecked(self.settings.steric_map)
        self.steric_map.setToolTip(
            "produce a 2D projection of steric bulk\ncauses buried volume to be reported for individual quadrants"
        )
        steric_layout.addRow("create steric map:", self.steric_map)

        self.num_pts = QSpinBox()
        self.num_pts.setRange(25, 250)
        self.num_pts.setValue(self.settings.num_pts)
        self.num_pts.setToolTip("number of points along x and y axes")
        steric_layout.addRow("number of points:", self.num_pts)

        self.include_vbur = QCheckBox()
        self.include_vbur.setChecked(self.settings.include_vbur)
        steric_layout.addRow("label quadrants with %V<sub>bur</sub>",
                             self.include_vbur)

        self.map_shape = QComboBox()
        self.map_shape.addItems(["circle", "square"])
        ndx = self.map_shape.findText(self.settings.map_shape, Qt.MatchExactly)
        self.map_shape.setCurrentIndex(ndx)
        steric_layout.addRow("map shape:", self.map_shape)

        self.auto_minmax = QCheckBox()
        self.auto_minmax.setChecked(self.settings.auto_minmax)
        steric_layout.addRow("automatic min. and max.:", self.auto_minmax)

        self.map_min = QDoubleSpinBox()
        self.map_min.setRange(-15., 0.)
        self.map_min.setSuffix(" \u212B")
        self.map_min.setSingleStep(0.1)
        self.map_min.setValue(self.settings.map_min)
        steric_layout.addRow("minimum value:", self.map_min)

        self.map_max = QDoubleSpinBox()
        self.map_max.setRange(0., 15.)
        self.map_max.setSuffix(" \u212B")
        self.map_max.setSingleStep(0.1)
        self.map_max.setValue(self.settings.map_max)
        steric_layout.addRow("maximum value:", self.map_max)

        self.num_pts.setEnabled(self.settings.steric_map)
        self.steric_map.stateChanged.connect(
            lambda state, widget=self.num_pts: widget.setEnabled(state == Qt.
                                                                 Checked))

        self.include_vbur.setEnabled(self.settings.steric_map)
        self.steric_map.stateChanged.connect(
            lambda state, widget=self.include_vbur: widget.setEnabled(
                state == Qt.Checked))

        self.map_shape.setEnabled(self.settings.steric_map)
        self.steric_map.stateChanged.connect(
            lambda state, widget=self.map_shape: widget.setEnabled(state == Qt.
                                                                   Checked))

        self.auto_minmax.setEnabled(self.settings.steric_map)
        self.steric_map.stateChanged.connect(
            lambda state, widget=self.auto_minmax: widget.setEnabled(
                state == Qt.Checked))

        self.map_min.setEnabled(not self.settings.auto_minmax
                                and self.settings.steric_map)
        self.steric_map.stateChanged.connect(
            lambda state, widget=self.map_min, widget2=self.auto_minmax: widget
            .setEnabled(state == Qt.Checked and not widget2.isChecked()))
        self.auto_minmax.stateChanged.connect(
            lambda state, widget=self.map_min, widget2=self.steric_map: widget.
            setEnabled(not state == Qt.Checked and widget2.isChecked()))

        self.map_max.setEnabled(not self.settings.auto_minmax
                                and self.settings.steric_map)
        self.steric_map.stateChanged.connect(
            lambda state, widget=self.map_max, widget2=self.auto_minmax: widget
            .setEnabled(state == Qt.Checked and not widget2.isChecked()))
        self.auto_minmax.stateChanged.connect(
            lambda state, widget=self.map_max, widget2=self.steric_map: widget.
            setEnabled(not state == Qt.Checked and widget2.isChecked()))

        self.display_cutout = QComboBox()
        self.display_cutout.addItems(["no", "free", "buried"])
        ndx = self.display_cutout.findText(self.settings.display_cutout,
                                           Qt.MatchExactly)
        self.display_cutout.setCurrentIndex(ndx)
        self.display_cutout.setToolTip("show free or buried volume")
        vol_cutout_layout.addRow("display volume:", self.display_cutout)

        self.point_spacing = QDoubleSpinBox()
        self.point_spacing.setDecimals(3)
        self.point_spacing.setRange(0.01, 0.5)
        self.point_spacing.setSingleStep(0.005)
        self.point_spacing.setSuffix(" \u212B")
        self.point_spacing.setValue(self.settings.point_spacing)
        self.point_spacing.setToolTip(
            "distance between points on cutout\n" +
            "smaller spacing will narrow gaps, but increase time to create the cutout"
        )
        vol_cutout_layout.addRow("point spacing:", self.point_spacing)

        self.intersection_scale = QDoubleSpinBox()
        self.intersection_scale.setDecimals(2)
        self.intersection_scale.setRange(1., 10.)
        self.intersection_scale.setSingleStep(0.5)
        self.intersection_scale.setSuffix("x")
        self.intersection_scale.setToolTip(
            "relative density of points where VDW radii intersect\n" +
            "higher density will narrow gaps, but increase time to create cutout"
        )
        self.intersection_scale.setValue(self.settings.intersection_scale)
        vol_cutout_layout.addRow("intersection density:",
                                 self.intersection_scale)

        self.cutout_labels = QComboBox()
        self.cutout_labels.addItems(["none", "quadrants", "octants"])
        ndx = self.cutout_labels.findText(self.settings.cutout_labels,
                                          Qt.MatchExactly)
        self.cutout_labels.setCurrentIndex(ndx)
        vol_cutout_layout.addRow("label sections:", self.cutout_labels)

        self.point_spacing.setEnabled(self.settings.display_cutout != "no")
        self.intersection_scale.setEnabled(
            self.settings.display_cutout != "no")
        self.cutout_labels.setEnabled(self.settings.display_cutout != "no")

        self.display_cutout.currentTextChanged.connect(
            lambda text, widget=self.point_spacing: widget.setEnabled(text !=
                                                                      "no"))
        self.display_cutout.currentTextChanged.connect(
            lambda text, widget=self.intersection_scale: widget.setEnabled(
                text != "no"))
        self.display_cutout.currentTextChanged.connect(
            lambda text, widget=self.cutout_labels: widget.setEnabled(text !=
                                                                      "no"))

        calc_vbur_button = QPushButton(
            "calculate % buried volume for selected centers")
        calc_vbur_button.clicked.connect(self.calc_vbur)
        calc_layout.addRow(calc_vbur_button)
        self.calc_vbur_button = calc_vbur_button

        remove_vbur_button = QPushButton(
            "remove % buried volume visualizations")
        remove_vbur_button.clicked.connect(self.del_vbur)
        vol_cutout_layout.addRow(remove_vbur_button)

        self.table = QTableWidget()
        self.table.setColumnCount(3)
        self.table.setHorizontalHeaderLabels(['model', 'center', '%Vbur'])
        self.table.setSelectionBehavior(QTableWidget.SelectRows)
        self.table.setEditTriggers(QTableWidget.NoEditTriggers)
        self.table.resizeColumnToContents(0)
        self.table.resizeColumnToContents(1)
        self.table.resizeColumnToContents(2)
        self.table.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.Interactive)
        self.table.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.Interactive)
        self.table.horizontalHeader().setSectionResizeMode(
            2, QHeaderView.Stretch)
        calc_layout.addRow(self.table)

        menu = QMenuBar()

        export = menu.addMenu("&Export")

        clear = QAction("Clear data table", self.tool_window.ui_area)
        clear.triggered.connect(self.clear_table)
        export.addAction(clear)

        copy = QAction("&Copy CSV to clipboard", self.tool_window.ui_area)
        copy.triggered.connect(self.copy_csv)
        shortcut = QKeySequence(Qt.CTRL + Qt.Key_C)
        copy.setShortcut(shortcut)
        export.addAction(copy)
        self.copy = copy

        save = QAction("&Save CSV...", self.tool_window.ui_area)
        save.triggered.connect(self.save_csv)
        #this shortcut interferes with main window's save shortcut
        #I've tried different shortcut contexts to no avail
        #thanks Qt...
        #shortcut = QKeySequence(Qt.CTRL + Qt.Key_S)
        #save.setShortcut(shortcut)
        #save.setShortcutContext(Qt.WidgetShortcut)
        export.addAction(save)

        delimiter = export.addMenu("Delimiter")

        comma = QAction("comma", self.tool_window.ui_area, checkable=True)
        comma.setChecked(self.settings.delimiter == "comma")
        comma.triggered.connect(lambda *args, delim="comma": self.settings.
                                __setattr__("delimiter", delim))
        delimiter.addAction(comma)

        tab = QAction("tab", self.tool_window.ui_area, checkable=True)
        tab.setChecked(self.settings.delimiter == "tab")
        tab.triggered.connect(lambda *args, delim="tab": self.settings.
                              __setattr__("delimiter", delim))
        delimiter.addAction(tab)

        space = QAction("space", self.tool_window.ui_area, checkable=True)
        space.setChecked(self.settings.delimiter == "space")
        space.triggered.connect(lambda *args, delim="space": self.settings.
                                __setattr__("delimiter", delim))
        delimiter.addAction(space)

        semicolon = QAction("semicolon",
                            self.tool_window.ui_area,
                            checkable=True)
        semicolon.setChecked(self.settings.delimiter == "semicolon")
        semicolon.triggered.connect(lambda *args, delim="semicolon": self.
                                    settings.__setattr__("delimiter", delim))
        delimiter.addAction(semicolon)

        add_header = QAction("&Include CSV header",
                             self.tool_window.ui_area,
                             checkable=True)
        add_header.setChecked(self.settings.include_header)
        add_header.triggered.connect(self.header_check)
        export.addAction(add_header)

        comma.triggered.connect(
            lambda *args, action=tab: action.setChecked(False))
        comma.triggered.connect(
            lambda *args, action=space: action.setChecked(False))
        comma.triggered.connect(
            lambda *args, action=semicolon: action.setChecked(False))

        tab.triggered.connect(
            lambda *args, action=comma: action.setChecked(False))
        tab.triggered.connect(
            lambda *args, action=space: action.setChecked(False))
        tab.triggered.connect(
            lambda *args, action=semicolon: action.setChecked(False))

        space.triggered.connect(
            lambda *args, action=comma: action.setChecked(False))
        space.triggered.connect(
            lambda *args, action=tab: action.setChecked(False))
        space.triggered.connect(
            lambda *args, action=semicolon: action.setChecked(False))

        semicolon.triggered.connect(
            lambda *args, action=comma: action.setChecked(False))
        semicolon.triggered.connect(
            lambda *args, action=tab: action.setChecked(False))
        semicolon.triggered.connect(
            lambda *args, action=space: action.setChecked(False))

        menu.setNativeMenuBar(False)
        self._menu = menu
        layout.setMenuBar(menu)
        menu.setVisible(True)

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)

    def clear_table(self):
        are_you_sure = QMessageBox.question(
            None,
            "Clear table?",
            "Are you sure you want to clear the data table?",
        )
        if are_you_sure != QMessageBox.Yes:
            return
        self.table.setRowCount(0)

    def set_ligand_atoms(self):
        self.ligand_atoms = selected_atoms(self.session)
        self.session.logger.status("set ligand to current selection")

    def calc_vbur(self):
        args = dict()

        cur_sel = selected_atoms(self.session)
        if len(cur_sel) == 0:
            return

        models = []
        for atom in cur_sel:
            if atom.structure not in models:
                models.append(atom.structure)

        center = []
        for atom in cur_sel:
            center.append(atom)

        args["center"] = center

        radii = self.radii_option.currentText()
        self.settings.radii = radii
        args["radii"] = radii

        scale = self.scale.value()
        self.settings.vdw_scale = scale
        args["scale"] = scale

        radius = self.radius.value()
        self.settings.center_radius = radius
        args["radius"] = radius

        steric_map = self.steric_map.checkState() == Qt.Checked
        self.settings.steric_map = steric_map
        args["steric_map"] = steric_map

        use_scene = self.use_scene.checkState() == Qt.Checked
        self.settings.use_scene = use_scene
        args["useScene"] = use_scene

        num_pts = self.num_pts.value()
        self.settings.num_pts = num_pts
        args["num_pts"] = num_pts

        include_vbur = self.include_vbur.checkState() == Qt.Checked
        self.settings.include_vbur = include_vbur

        use_centroid = self.use_centroid.checkState() == Qt.Checked
        self.settings.use_centroid = use_centroid
        args["useCentroid"] = use_centroid

        shape = self.map_shape.currentText()
        self.settings.map_shape = shape
        args["shape"] = shape

        report_component = self.report_component.currentText()
        self.settings.report_component = report_component
        args["reportComponent"] = report_component

        method = self.method.currentText()
        self.settings.method = method

        if method == "Lebedev":
            args["method"] = "lebedev"
            rad_pts = self.radial_points.currentText()
            self.settings.radial_points = rad_pts
            args["radialPoints"] = rad_pts

            ang_pts = self.angular_points.currentText()
            self.settings.angular_points = ang_pts
            args["angularPoints"] = ang_pts

        elif method == "Monte-Carlo":
            args["method"] = "mc"
            min_iter = self.min_iter.value()
            self.settings.minimum_iterations = min_iter
            args["minimumIterations"] = min_iter

        display_cutout = self.display_cutout.currentText()
        self.settings.display_cutout = display_cutout
        if display_cutout != "no":
            args["displaySphere"] = display_cutout

        if display_cutout != "no":
            point_spacing = self.point_spacing.value()
            self.settings.point_spacing = point_spacing
            args["pointSpacing"] = point_spacing

            intersection_scale = self.intersection_scale.value()
            self.settings.intersection_scale = intersection_scale
            args["intersectionScale"] = intersection_scale

            cutout_labels = self.cutout_labels.currentText()
            self.settings.cutout_labels = cutout_labels
            args["labels"] = cutout_labels

        if len(self.ligand_atoms) > 0:
            args["onlyAtoms"] = [a for a in self.ligand_atoms if not a.deleted]
            if len(args["onlyAtoms"]) == 0:
                args["onlyAtoms"] = None

        auto_minmax = self.auto_minmax.checkState() == Qt.Checked
        self.settings.auto_minmax = auto_minmax
        if not auto_minmax:
            map_max = self.map_max.value()
            self.settings.map_max = map_max

            map_min = self.settings.map_min
            self.settings.map_min = map_min

        info = percent_vbur_cmd(self.session,
                                models,
                                return_values=True,
                                **args)

        # self.table.setRowCount(0)

        if steric_map:
            for mdl, cent, vbur, map_info in info:
                row = self.table.rowCount()
                self.table.insertRow(row)

                m = QTableWidgetItem()
                m.setData(Qt.DisplayRole, mdl.name)
                self.table.setItem(row, 0, m)

                c = QTableWidgetItem()
                c.setData(Qt.DisplayRole, cent)
                self.table.setItem(row, 1, c)

                v = QTableWidgetItem()
                if report_component == "octants":
                    v.setData(Qt.DisplayRole,
                              ",".join(["%.1f" % x for x in vbur]))
                elif report_component == "quadrants":
                    v.setData(
                        Qt.DisplayRole, ",".join("%.1f" % x for x in [
                            vbur[0] + vbur[7],
                            vbur[1] + vbur[6],
                            vbur[2] + vbur[5],
                            vbur[3] + vbur[4],
                        ]))
                else:
                    if hasattr(vbur, "__iter__"):
                        v.setData(Qt.DisplayRole, "%.1f" % sum(vbur))
                    else:
                        v.setData(Qt.DisplayRole, "%.1f" % vbur)
                v.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
                self.table.setItem(row, 2, v)

                x, y, z, min_alt, max_alt = map_info
                plot = self.tool_window.create_child_window(
                    "steric map of %s" % mdl.name, window_class=StericMap)
                if auto_minmax:
                    plot.set_data(x, y, z, min_alt, max_alt, vbur, radius,
                                  include_vbur)
                else:
                    plot.set_data(x, y, z, map_min, map_max, vbur, radius,
                                  include_vbur)

        else:
            for mdl, cent, vbur in info:
                row = self.table.rowCount()
                self.table.insertRow(row)

                m = QTableWidgetItem()
                m.setData(Qt.DisplayRole, mdl.name)
                self.table.setItem(row, 0, m)

                c = QTableWidgetItem()
                c.setData(Qt.DisplayRole, cent)
                self.table.setItem(row, 1, c)

                v = QTableWidgetItem()
                if report_component == "octants":
                    v.setData(Qt.DisplayRole,
                              ",".join(["%.1f" % x for x in vbur]))
                elif report_component == "quadrants":
                    v.setData(
                        Qt.DisplayRole, ",".join("%.1f" % x for x in [
                            vbur[0] + vbur[7],
                            vbur[1] + vbur[6],
                            vbur[2] + vbur[5],
                            vbur[3] + vbur[4],
                        ]))
                else:
                    if hasattr(vbur, "__iter__"):
                        v.setData(Qt.DisplayRole, "%.1f" % sum(vbur))
                    else:
                        v.setData(Qt.DisplayRole, "%.1f" % vbur)
                v.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
                self.table.setItem(row, 2, v)

        self.table.resizeColumnToContents(1)
        self.table.resizeColumnToContents(2)

    def header_check(self, state):
        """user has [un]checked the 'include header' option on the menu"""
        if state:
            self.settings.include_header = True
        else:
            self.settings.include_header = False

    def get_csv(self):
        if self.settings.delimiter == "comma":
            delim = ","
        elif self.settings.delimiter == "space":
            delim = " "
        elif self.settings.delimiter == "tab":
            delim = "\t"
        elif self.settings.delimiter == "semicolon":
            delim = ";"

        if self.settings.include_header:
            s = delim.join(["model", "center", "%Vbur"])
            s += "\n"
        else:
            s = ""

        for i in range(0, self.table.rowCount()):
            s += delim.join([
                item.data(Qt.DisplayRole)
                for item in [self.table.item(i, j) for j in range(0, 3)]
            ])
            s += "\n"

        return s

    def copy_csv(self):
        app = QApplication.instance()
        clipboard = app.clipboard()
        csv = self.get_csv()
        clipboard.setText(csv)
        self.session.logger.status("copied to clipboard")

    def save_csv(self):
        """save data on current tab to CSV file"""
        filename, _ = QFileDialog.getSaveFileName(filter="CSV Files (*.csv)")
        if filename:
            s = self.get_csv()

            with open(filename, 'w') as f:
                f.write(s.strip())

            self.session.logger.status("saved to %s" % filename)

    def del_vbur(self):
        for model in self.session.models.list(type=Surface):
            if model.name.startswith("%Vbur") or model.name.startswith(
                    "%Vfree"):
                model.delete()

    def display_help(self):
        """Show the help for this tool in the help viewer."""
        from chimerax.core.commands import run
        run(self.session,
            'open %s' % self.help if self.help is not None else "")
예제 #12
0
class EditStructure(ToolInstance):

    help = "https://github.com/QChASM/SEQCROW/wiki/Structure-Modification-Tool"
    SESSION_ENDURING = False
    SESSION_SAVE = False

    def __init__(self, session, name):       
        super().__init__(session, name)
        
        self.settings = _EditStructureSettings(session, "Structure Modification")
        
        self.tool_window = MainToolWindow(self)        

        self.close_previous_bool = self.settings.modify

        self._build_ui()

    def _build_ui(self):
        layout = QGridLayout()
        
        self.alchemy_tabs = QTabWidget()
        
        #substitute
        substitute_tab = QWidget()
        substitute_layout = QGridLayout(substitute_tab) 

        sublabel = QLabel("substituent name:")
        substitute_layout.addWidget(sublabel, 0, 0, Qt.AlignVCenter)
        
        self.subname = QLineEdit()
        # self.subname.setText("Et")
        sub_completer = NameCompleter(Substituent.list(), self.subname)
        self.subname.setCompleter(sub_completer)
        self.subname.setToolTip("name of substituent in the AaronTools library or your personal library\nseparate names with commas and uncheck 'modify selected structure' to create several structures")
        substitute_layout.addWidget(self.subname, 0, 1, Qt.AlignVCenter)
        
        open_sub_lib = QPushButton("from library...")
        open_sub_lib.clicked.connect(self.open_sub_selector)
        substitute_layout.addWidget(open_sub_lib, 0, 2, Qt.AlignTop)        
        
        substitute_layout.addWidget(QLabel("modify selected structure:"), 1, 0, 1, 1, Qt.AlignVCenter)
        
        self.close_previous_sub = QCheckBox()
        self.close_previous_sub.setToolTip("checked: selected structure will be modified\nunchecked: new model will be created for the modified structure")
        self.close_previous_sub.setChecked(self.settings.modify)
        self.close_previous_sub.stateChanged.connect(self.close_previous_change)
        substitute_layout.addWidget(self.close_previous_sub, 1, 1, 1, 2, Qt.AlignTop)    
        
        substitute_layout.addWidget(QLabel("relax substituent:"), 2, 0, 1, 1, Qt.AlignVCenter)
        
        self.minimize = QCheckBox()
        self.minimize.setToolTip("spin the added substituents to try to minimize the LJ potential energy")
        self.minimize.setChecked(self.settings.minimize)
        substitute_layout.addWidget(self.minimize, 2, 1, 1, 1, Qt.AlignTop)
        
        substitute_layout.addWidget(QLabel("guess previous substituent:"), 3, 0, 1, 1, Qt.AlignVCenter)
        
        self.guess_old = QCheckBox()
        self.guess_old.setToolTip("checked: leave the longest connected fragment in the residue\nunchecked: previous substituent must be selected")
        self.guess_old.setChecked(self.settings.guess)
        self.guess_old.stateChanged.connect(lambda state, settings=self.settings: settings.__setattr__("guess", True if state == Qt.Checked else False))
        substitute_layout.addWidget(self.guess_old, 3, 1, 1, 2, Qt.AlignTop)
        
        substitute_layout.addWidget(QLabel("new residue:"), 5, 0, 1, 1, Qt.AlignVCenter)

        self.new_residue = QCheckBox()
        self.new_residue.setToolTip("put the new substituent in its own residue instead\nof adding it to the residue of the old substituent")
        self.new_residue.setChecked(self.settings.new_residue)
        self.new_residue.stateChanged.connect(lambda state, settings=self.settings: settings.__setattr__("new_residue", True if state == Qt.Checked else False))
        substitute_layout.addWidget(self.new_residue, 5, 1, 1, 2, Qt.AlignTop)
        
        substitute_layout.addWidget(QLabel("use distance names:"), 4, 0, 1, 1, Qt.AlignVCenter)
        
        self.use_greek = QCheckBox()
        self.use_greek.setChecked(self.settings.use_greek)
        self.use_greek.setToolTip("indicate distance from point of attachment with atom name")
        substitute_layout.addWidget(self.use_greek, 4, 1, 1, 1, Qt.AlignTop)

        substitute_layout.addWidget(QLabel("change residue name:"), 6, 0, 1, 1, Qt.AlignVCenter)
        
        self.new_sub_name = QLineEdit()
        self.new_sub_name.setToolTip("change name of modified residues")
        self.new_sub_name.setPlaceholderText("leave blank to keep current")
        substitute_layout.addWidget(self.new_sub_name, 6, 1, 1, 2, Qt.AlignTop)

        substitute_button = QPushButton("substitute current selection")
        substitute_button.clicked.connect(self.do_substitute)
        substitute_layout.addWidget(substitute_button, 7, 0, 1, 3, Qt.AlignTop)
        self.substitute_button = substitute_button
        
        substitute_layout.setRowStretch(0, 0)
        substitute_layout.setRowStretch(1, 0)
        substitute_layout.setRowStretch(2, 0)
        substitute_layout.setRowStretch(3, 0)
        substitute_layout.setRowStretch(4, 0)
        substitute_layout.setRowStretch(5, 0)
        substitute_layout.setRowStretch(6, 0)
        substitute_layout.setRowStretch(7, 1)
        
        
        #map ligand
        maplig_tab = QWidget()
        maplig_layout = QGridLayout(maplig_tab)
        
        liglabel = QLabel("ligand name:")
        maplig_layout.addWidget(liglabel, 0, 0, Qt.AlignVCenter)
        
        self.ligname = QLineEdit()
        lig_completer = NameCompleter(Component.list(), self.ligname)
        self.ligname.setCompleter(lig_completer)
        self.ligname.setToolTip("name of ligand in the AaronTools library or your personal library\nseparate names with commas and uncheck 'modify selected structure' to create several structures")
        maplig_layout.addWidget(self.ligname, 0, 1, Qt.AlignVCenter)
        
        open_lig_lib = QPushButton("from library...")
        open_lig_lib.clicked.connect(self.open_lig_selector)
        maplig_layout.addWidget(open_lig_lib, 0, 2, Qt.AlignTop)        
        
        maplig_layout.addWidget(QLabel("modify selected structure:"), 1, 0, 1, 1, Qt.AlignVCenter)
        
        self.close_previous_lig = QCheckBox()
        self.close_previous_lig.setToolTip("checked: selected structure will be modified\nunchecked: new model will be created for the modified structure")
        self.close_previous_lig.setChecked(self.settings.modify)
        self.close_previous_lig.stateChanged.connect(self.close_previous_change)
        maplig_layout.addWidget(self.close_previous_lig, 1, 1, 1, 2, Qt.AlignTop)

        maplig_button = QPushButton("swap ligand with selected coordinating atoms")
        maplig_button.clicked.connect(self.do_maplig)
        maplig_layout.addWidget(maplig_button, 2, 0, 1, 3, Qt.AlignTop)
        self.maplig_button = maplig_button

        start_structure_button = QPushButton("place in:")
        self.lig_model_selector = ModelComboBox(self.session, addNew=True)
        start_structure_button.clicked.connect(self.do_new_lig)
        maplig_layout.addWidget(start_structure_button, 3, 0, 1, 1, Qt.AlignTop)
        maplig_layout.addWidget(self.lig_model_selector, 3, 1, 1, 2, Qt.AlignTop)

        maplig_layout.setRowStretch(0, 0)
        maplig_layout.setRowStretch(1, 0)
        maplig_layout.setRowStretch(2, 0)
        maplig_layout.setRowStretch(3, 1)
        
        
        #close ring
        closering_tab = QWidget()
        closering_layout = QGridLayout(closering_tab)
        
        ringlabel = QLabel("ring name:")
        closering_layout.addWidget(ringlabel, 0, 0, Qt.AlignVCenter)
        
        self.ringname = QLineEdit()
        ring_completer = NameCompleter(Ring.list(), self.ringname)
        self.ringname.setCompleter(ring_completer)
        self.ringname.setToolTip("name of ring in the AaronTools library or your personal library\nseparate names with commas and uncheck 'modify selected structure' to create several structures")
        closering_layout.addWidget(self.ringname, 0, 1, Qt.AlignVCenter)
        
        open_ring_lib = QPushButton("from library...")
        open_ring_lib.clicked.connect(self.open_ring_selector)
        closering_layout.addWidget(open_ring_lib, 0, 2, Qt.AlignTop)        
        
        closering_layout.addWidget(QLabel("modify selected structure:"), 1, 0, 1, 1, Qt.AlignVCenter) 
        
        self.close_previous_ring = QCheckBox()
        self.close_previous_ring.setToolTip("checked: selected structure will be modified\nunchecked: new model will be created for the modified structure")
        self.close_previous_ring.setChecked(self.settings.modify)
        self.close_previous_ring.stateChanged.connect(self.close_previous_change)
        closering_layout.addWidget(self.close_previous_ring, 1, 1, 1, 2, Qt.AlignTop)

        closering_layout.addWidget(QLabel("try multiple:"), 2, 0, 1, 1, Qt.AlignVCenter)

        self.minimize_ring = QCheckBox()
        self.minimize_ring.setToolTip("try to use other versions of this ring in the library to find the one that fits best")
        self.minimize_ring.setChecked(self.settings.minimize_ring)
        closering_layout.addWidget(self.minimize_ring, 2, 1, 1, 2, Qt.AlignTop)

        closering_layout.addWidget(QLabel("new residue name:"), 3, 0, 1, 1, Qt.AlignVCenter)
        
        self.new_ring_name = QLineEdit()
        self.new_ring_name.setToolTip("change name of modified residues")
        self.new_ring_name.setPlaceholderText("leave blank to keep current")
        closering_layout.addWidget(self.new_ring_name, 3, 1, 1, 2, Qt.AlignTop)

        closering_button = QPushButton("put a ring on current selection")
        closering_button.clicked.connect(self.do_fusering)

        closering_layout.addWidget(closering_button, 4, 0, 1, 3, Qt.AlignTop)
        self.closering_button = closering_button

        start_structure_button = QPushButton("place in:")
        self.ring_model_selector = ModelComboBox(self.session, addNew=True)
        start_structure_button.clicked.connect(self.do_new_ring)
        closering_layout.addWidget(start_structure_button, 5, 0, 1, 1, Qt.AlignTop)
        closering_layout.addWidget(self.ring_model_selector, 5, 1, 1, 2, Qt.AlignTop)

        closering_layout.setRowStretch(0, 0)
        closering_layout.setRowStretch(1, 0)
        closering_layout.setRowStretch(2, 0)
        closering_layout.setRowStretch(3, 0)
        closering_layout.setRowStretch(4, 0)
        closering_layout.setRowStretch(5, 1)


        #change element
        changeelement_tab = QWidget()
        changeelement_layout = QFormLayout(changeelement_tab)
        
        self.element = ElementButton("C", single_state=True)
        self.element.clicked.connect(self.open_ptable)
        changeelement_layout.addRow("element:", self.element)
        
        self.vsepr = QComboBox()
        self.vsepr.addItems([
            "do not change",                  # 0
            
            "linear (1 bond)",                # 1
            
            "linear (2 bonds)",               # 2 
            "trigonal planar (2 bonds)",      # 3
            "tetrahedral (2 bonds)",          # 4 
            
            "trigonal planar",                # 5
            "tetrahedral (3 bonds)",          # 6
            "T-shaped",                       # 7
            
            "trigonal pyramidal",             # 8
            "tetrahedral",                    # 9
            "sawhorse",                       #10
            "seesaw",                         #11
            "square planar",                  #12
            
            "trigonal bipyramidal",           #13
            "square pyramidal",               #14
            "pentagonal",                     #15
            
            "octahedral",                     #16
            "hexagonal",                      #17
            "trigonal prismatic",             #18
            "pentagonal pyramidal",           #19
            
            "capped octahedral",              #20
            "capped trigonal prismatic",      #21
            "heptagonal",                     #22
            "hexagonal pyramidal",            #23
            "pentagonal bipyramidal",         #24
            
            "biaugmented trigonal prismatic", #25
            "cubic",                          #26
            "elongated trigonal bipyramidal", #27
            "hexagonal bipyramidal",          #28
            "heptagonal pyramidal",           #29
            "octagonal",                      #30
            "square antiprismatic",           #31
            "trigonal dodecahedral",          #32
            
            "capped cube",                    #33
            "capped square antiprismatic",    #34
            "enneagonal",                     #35
            "heptagonal bipyramidal",         #36
            "hula-hoop",                      #37
            "triangular cupola",              #38
            "tridiminished icosahedral",      #39
            "muffin",                         #40
            "octagonal pyramidal",            #41
            "tricapped trigonal prismatic",   #42
        ])
        
        self.vsepr.setCurrentIndex(9)
        
        self.vsepr.insertSeparator(33)
        self.vsepr.insertSeparator(25)
        self.vsepr.insertSeparator(20)
        self.vsepr.insertSeparator(16)
        self.vsepr.insertSeparator(13)
        self.vsepr.insertSeparator(8)
        self.vsepr.insertSeparator(5)
        self.vsepr.insertSeparator(2)
        self.vsepr.insertSeparator(1)
        self.vsepr.insertSeparator(0)
        changeelement_layout.addRow("geometry:", self.vsepr)
        
        self.change_bonds = QCheckBox()
        self.change_bonds.setChecked(self.settings.change_bonds)
        changeelement_layout.addRow("adjust bond lengths:", self.change_bonds)
        
        change_element_button = QPushButton("change selected elements")
        change_element_button.clicked.connect(self.do_change_element)
        changeelement_layout.addRow(change_element_button)
        self.change_element_button = change_element_button

        start_structure_button = QPushButton("place in:")
        self.model_selector = ModelComboBox(self.session, addNew=True)
        start_structure_button.clicked.connect(self.do_new_atom)
        changeelement_layout.addRow(start_structure_button, self.model_selector)
        
        delete_atoms_button = QPushButton("delete selected atoms")
        delete_atoms_button.clicked.connect(self.delete_atoms)
        changeelement_layout.addRow(delete_atoms_button)

        self.alchemy_tabs.addTab(substitute_tab, "substitute")
        self.alchemy_tabs.addTab(maplig_tab, "swap ligand")
        self.alchemy_tabs.addTab(closering_tab, "fuse ring")
        self.alchemy_tabs.addTab(changeelement_tab, "change element")

        layout.addWidget(self.alchemy_tabs)

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)
    
    def close_previous_change(self, state):
        if state == Qt.Checked:
            self.settings.modify = True
            for checkbox in [self.close_previous_lig, self.close_previous_sub, self.close_previous_ring]:
                checkbox.setChecked(True)
            self.close_previous_bool = True
        else:
            self.settings.modify = False
            for checkbox in [self.close_previous_lig, self.close_previous_sub, self.close_previous_ring]:
                checkbox.setChecked(False)
            self.close_previous_bool = False
    
    def do_substitute(self):
        subnames = self.subname.text()
        
        new_name = self.new_sub_name.text()

        use_attached = not self.guess_old.isChecked()
        
        minimize = self.minimize.isChecked()
        
        new_residue = self.new_residue.isChecked()

        use_greek = self.use_greek.isChecked()
        
        self.settings.minimize = minimize
        self.settings.use_greek = use_greek

        if len(new_name.strip()) > 0:
            run(
                self.session, 
                "substitute sel substituents %s newName %s guessAttachment %s modify %s minimize %s useRemoteness %s newResidue %s" %
                (
                    subnames,
                    new_name,
                    not use_attached,
                    self.close_previous_bool,
                    minimize,
                    use_greek,
                    new_residue,
                )
            )

        else:
            run(
                self.session,
                "substitute sel substituents %s guessAttachment %s modify %s minimize %s useRemoteness %s newResidue %s" %
                (
                    subnames,
                    not use_attached,
                    self.close_previous_bool,
                    minimize,
                    use_greek,
                    new_residue,
                )
            )

    def open_sub_selector(self):
        self.tool_window.create_child_window("select substituents", window_class=SubstituentSelection, textBox=self.subname)

    def do_maplig(self):
        lignames = self.ligname.text()
        selection = selected_atoms(self.session)
        
        if len(selection) < 1:
            raise RuntimeWarning("nothing selected")
        
        models = {}
        for atom in selection:
            if atom.structure not in models:
                models[atom.structure] = [AtomSpec(atom.atomspec)]
            else:
                models[atom.structure].append(AtomSpec(atom.atomspec))        
        
        first_pass = True
        new_structures = []
        for ligname in lignames.split(','):
            ligname = ligname.strip()
            lig = Component(ligname)
            for model in models:
                if self.close_previous_bool and first_pass:
                    rescol = ResidueCollection(model)
                elif self.close_previous_bool and not first_pass:
                    raise RuntimeError("only the first model can be replaced")
                else:
                    model_copy = model.copy()
                    rescol = ResidueCollection(model_copy)
                    for i, atom in enumerate(model.atoms):
                        rescol.atoms[i].atomspec = atom.atomspec
                        rescol.atoms[i].add_tag(atom.atomspec)
                        rescol.atoms[i].chix_atom = atom

                target = rescol.find(models[model])
                if len(target) % len(lig.key_atoms) == 0:
                    k = 0
                    ligands = []
                    while k != len(target):
                        res_lig = ResidueCollection(lig.copy(), comment=lig.comment)
                        res_lig.parse_comment()
                        res_lig = Component(res_lig, key_atoms = ",".join([str(k + 1) for k in res_lig.other["key_atoms"]]))
                        ligands.append(res_lig)
                        k += len(lig.key_atoms)
                else:
                    raise RuntimeError("number of key atoms no not match: %i now, new ligand has %i" % (len(target), len(lig.key_atoms)))
                
                rescol.map_ligand(ligands, target)

                for center_atom in rescol.center:
                    center_atom.connected = set([])
                    for atom in rescol.atoms:
                        if atom not in rescol.center:
                            if center_atom.is_connected(atom):
                                atom.connected.add(center_atom)
                                center_atom.connected.add(atom)
                
                if self.close_previous_bool:    
                    rescol.update_chix(model)
                else:
                    struc = rescol.get_chimera(self.session)
                    new_structures.append(struc)
            
            first_pass = False
        
        if not self.close_previous_bool:
            self.session.models.add(new_structures)

    def open_lig_selector(self):
        self.tool_window.create_child_window("select ligands", window_class=LigandSelection, textBox=self.ligname)
    
    def do_fusering(self):
        ring_names = self.ringname.text()
        
        new_name = self.new_ring_name.text()

        minimize_ring = self.minimize_ring.isChecked()
        self.settings.minimize_ring = minimize_ring

        if len(new_name.strip()) > 0:
            run(
                self.session,
                "fuseRing sel rings %s newName %s modify %s minimize %s" % (
                    ring_names,
                    new_name,
                    self.close_previous_bool,
                    minimize_ring,
                )
            )

        else:
            run(
                self.session,
                "fuseRing sel rings %s modify %s minimize %s" % (
                    ring_names,
                    self.close_previous_bool,
                    minimize_ring,
                )
            )

    def open_ring_selector(self):
        self.tool_window.create_child_window("select rings", window_class=RingSelection, textBox=self.ringname)

    def open_ptable(self):
        self.tool_window.create_child_window("select element", window_class=_PTable, button=self.element)
    
    def display_help(self):
        """Show the help for this tool in the help viewer."""
        from chimerax.core.commands import run
        run(self.session,
            'open %s' % self.help if self.help is not None else "")
    
    def do_change_element(self):
        element = self.element.text()
        adjust_bonds = self.change_bonds.isChecked()
        self.settings.change_bonds = adjust_bonds
        vsepr = self.vsepr.currentText()
        
        if vsepr == "do not change":
            vsepr = False
        elif vsepr == "linear (1 bond)":
            vsepr = "linear 1"
            goal = 1
        elif vsepr == "linear (2 bonds)":
            vsepr = "linear 2"
            goal = 2
        elif vsepr == "trigonal planar (2 bonds)":
            vsepr = "bent 2 planar"
            goal = 2
        elif vsepr == "tetrahedral (2 bonds)":
            vsepr = "bent 2 tetrahedral"
            goal = 2
        elif vsepr == "trigonal planar":
            goal = 3
        elif vsepr == "tetrahedral (3 bonds)":
            vsepr = "bent 3 tetrahedral"
            goal = 3
        else:
            goal = len(Atom.get_shape(vsepr)) - 1
        
        sel = selected_atoms(self.session)
        models, _ = guessAttachmentTargets(sel, self.session, allow_adjacent=False)
        for model in models:
            conv_res = list(models[model].keys())
            for res in models[model]:
                for target in models[model][res]:
                    for neighbor in target.neighbors:
                        if neighbor.residue not in conv_res:
                            conv_res.append(neighbor.residue)
            
                    for pbg in self.session.models.list(type=PseudobondGroup):
                        for pbond in pbg.pseudobonds:
                            if target in pbond.atoms and all(atom.structure is model for atom in pbond.atoms):
                                other_atom = pbond.other_atom(target)
                                if other_atom.residue not in conv_res:
                                    conv_res.append(other_atom.residue)
            
            rescol = ResidueCollection(model, convert_residues=conv_res)
            for res in models[model]:
                residue = [resi for resi in rescol.residues if resi.chix_residue is res][0]
                
                for target in models[model][res]:
                    targ = rescol.find_exact(AtomSpec(target.atomspec))[0]
                    adjust_hydrogens = vsepr
                    if vsepr is not False:
                        cur_bonds = len(targ.connected)
                        change_Hs = goal - cur_bonds
                        adjust_hydrogens = (change_Hs, vsepr)

                    residue.change_element(targ, 
                                           element, 
                                           adjust_bonds=adjust_bonds, 
                                           adjust_hydrogens=adjust_hydrogens,
                    )
                
                residue.update_chix(res)    
    
    def do_new_ring(self):
        rings = self.ringname.text()
        
        for ring in rings.split(","):
            ring = ring.strip()
            
            rescol = ResidueCollection(Ring(ring))
        
            model = self.ring_model_selector.currentData()
            if model is None:
                chix = rescol.get_chimera(self.session)
                self.session.models.add([chix])
                apply_seqcrow_preset(chix, fallback="Ball-Stick-Endcap")
                self.ring_model_selector.setCurrentIndex(self.ring_model_selector.count()-1)
    
            else:
                res = model.new_residue("new", "a", len(model.residues)+1)
                rescol.residues[0].update_chix(res)
                run(self.session, "select add %s" % " ".join([atom.atomspec for atom in res.atoms]))
    
    def do_new_lig(self):
        ligands = self.ligname.text()
        
        for lig in ligands.split(","):
            lig = lig.strip()
            
            rescol = ResidueCollection(Component(lig))
        
            model = self.lig_model_selector.currentData()
            if model is None:
                chix = rescol.get_chimera(self.session)
                self.session.models.add([chix])
                apply_seqcrow_preset(chix, fallback="Ball-Stick-Endcap")
                self.lig_model_selector.setCurrentIndex(self.lig_model_selector.count()-1)
    
            else:
                res = model.new_residue("new", "a", len(model.residues)+1)
                rescol.residues[0].update_chix(res)
                run(self.session, "select add %s" % " ".join([atom.atomspec for atom in res.atoms]))

    def do_new_atom(self):
        element = self.element.text()
        adjust_bonds = self.change_bonds.isChecked()
        self.settings.change_bonds = adjust_bonds
        vsepr = self.vsepr.currentText()
        
        if vsepr == "do not change":
            vsepr = False
        elif vsepr == "linear (1 bond)":
            vsepr = "linear 1"
        elif vsepr == "linear (2 bonds)":
            vsepr = "linear 2"
        elif vsepr == "trigonal planar (2 bonds)":
            vsepr = "bent 2 planar"
        elif vsepr == "tetrahedral (2 bonds)":
            vsepr = "bent 2 tetrahedral"
        elif vsepr == "tetrahedral (3 bonds)":
            vsepr = "bent 3 tetrahedral"
        
        if vsepr:
            atoms = Atom.get_shape(vsepr)
            atoms[0].element = element
            for atom in atoms[1:]:
                atom.element = "H"
            if adjust_bonds:
                # this works b/c all atoms are 1 angstrom from the center
                # just multiply by the distance we want
                expected_dist = RADII[element] + RADII["H"]
                for atom in atoms[1:]:
                    atom.coords *= expected_dist
            for atom in atoms[1:]:
                atoms[0].connected.add(atom)
                atom.connected.add(atoms[0])
        else:
            atoms = [Atom(element=element, coords=np.zeros(3))]

        rescol = ResidueCollection(atoms, name="new", refresh_connected=False)

        model = self.model_selector.currentData()
        if model is None:
            chix = rescol.get_chimera(self.session)
            self.session.models.add([chix])
            apply_seqcrow_preset(chix, fallback="Ball-Stick-Endcap")
            self.model_selector.setCurrentIndex(self.model_selector.count()-1)

        else:
            res = model.new_residue("new", "a", len(model.residues)+1)
            rescol.residues[0].update_chix(res)
            run(self.session, "select add %s" % " ".join([atom.atomspec for atom in res.atoms]))
    
    def delete_atoms(self, *args):
        atoms = selected_atoms(self.session)
        for atom in atoms:
            atom.delete()
    
    def delete(self):
        self.ring_model_selector.deleteLater()
        self.lig_model_selector.deleteLater()
        self.model_selector.deleteLater()

        return super().delete()    
    
    def close(self):
        self.ring_model_selector.deleteLater()
        self.lig_model_selector.deleteLater()
        self.model_selector.deleteLater()

        return super().close()
예제 #13
0
class Linter(ToolInstance):
    """
    tool to run python linters on files
    the UI has a list of files, an option to choose
    the linter, and a button to run the linter
    results are printed to the log
    """
    def __init__(self, session, name):
        super().__init__(session, name)

        self.tool_window = MainToolWindow(self)
        self.settings = _LinterSettings(self.session, name)

        self._build_ui()

    def _build_ui(self):
        layout = QFormLayout()

        self.table = QTableWidget()
        self.table.setColumnCount(2)
        self.table.setHorizontalHeaderLabels(["file", "remove"])
        self.table.horizontalHeader().setStretchLastSection(False)
        self.table.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.Stretch)
        self.table.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.Fixed)
        self.table.cellClicked.connect(self.table_clicked)
        layout.addRow(self.table)

        self.linters = QComboBox()
        self.linters.addItems([
            "pyflakes",
            "flake8",
            "mypy",
            "pydocstyle",
            "pylint",
        ])
        ndx = self.linters.findText(self.settings.linter, Qt.MatchExactly)
        self.linters.setCurrentIndex(ndx)
        layout.addRow(self.linters)

        lint = QPushButton("run linter")
        lint.clicked.connect(self.run_linter)
        layout.addRow(lint)

        self.add_files(loads(self.settings.files))

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)

    def table_clicked(self, row, column):
        """
        if the last row is clicked, open a file browser and add
        the files to the list
        otherwise, if the 2nd column is clicked (the trash can),
        remove that row
        """
        if row == self.table.rowCount() - 1 or self.table.rowCount() == 1:
            filenames = QFileDialog.getOpenFileNames(
                filter="Python Files (*.py)")
            if filenames[0]:
                self.table.setRowCount(self.table.rowCount() - 1)
                self.add_files(filenames[0])
        elif column == 1:
            self.table.removeRow(row)

    def add_files(self, filenames):
        """add filenames (list(str)) to the table"""
        for f in filenames:
            row = self.table.rowCount()
            self.table.insertRow(row)

            file_item = QTableWidgetItem()
            file_item.setData(Qt.DisplayRole, f)
            self.table.setItem(row, 0, file_item)

            widget_that_lets_me_horizontally_align_an_icon = QWidget()
            widget_layout = QHBoxLayout(
                widget_that_lets_me_horizontally_align_an_icon)
            section_remove = QLabel()
            dim = int(1.5 *
                      section_remove.fontMetrics().boundingRect("Z").height())
            section_remove.setPixmap(
                QIcon(section_remove.style().standardIcon(
                    QStyle.SP_DialogDiscardButton)).pixmap(dim, dim))
            widget_layout.addWidget(section_remove, 0, Qt.AlignHCenter)
            widget_layout.setContentsMargins(0, 0, 0, 0)
            self.table.setCellWidget(
                row, 1, widget_that_lets_me_horizontally_align_an_icon)

        self.add_last_row()

    def add_last_row(self):
        """add the "add files" button to the bottom of the table"""
        row = self.table.rowCount()
        self.table.insertRow(row)

        add_row = QTableWidgetItem()
        add_row.setData(Qt.DisplayRole, "add files")
        self.table.setItem(row, 0, add_row)

    def run_linter(self):
        """execute linter"""
        previous_files = []
        linter = self.linters.currentText()
        for row in range(0, self.table.rowCount() - 1):
            fname = self.table.item(row, 0).text()
            previous_files.append(fname)

            run(self.session, "linter \"%s\" linter %s" % (fname, linter))

        self.settings.files = dumps(previous_files)
        self.settings.linter = linter