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
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
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))
class RowTableWidget(QFrame): double_clicked = Signal(object) def __init__(self, auto_resize=False, single_row_select=True, context_menu_callback=None, last_column_stretch=True, has_counters=False, parent=None ): QFrame.__init__(self, parent) self._has_counters = has_counters self.model = None self.table_view = RowTableView(auto_resize, single_row_select, context_menu_callback, last_column_stretch) self.table_view.doubleClicked.connect(self._double_clicked) if has_counters: self.counters = Counters() self.counters.button_clicked.connect(self._counter_clicked) self.search_bar = QLineEdit() self.search_bar.setFixedHeight(SEARCHBAR_HEIGHT) self.search_bar.textChanged.connect(self.set_search_text) self.search_bar.setToolTip("Search bar") self.auto_size_button = QPushButton('<>') self.auto_size_button.setFixedSize(SEARCHBAR_HEIGHT, SEARCHBAR_HEIGHT) self.auto_size_button.clicked.connect(self._auto_size_clicked) self.auto_size_button.setToolTip("Auto size") self.status_label = QLabel(STATUS_LABEL_MESSAGE.format(0, 0)) self.status_label.setFixedWidth(STATUS_LABEL_WIDTH) self.progress_bar = QProgressBar() self.progress_bar.setFormat('') layout = QGridLayout() layout.addWidget(self.search_bar, 0, 0, 1, 3) layout.addWidget(self.auto_size_button, 0, 3) if has_counters: layout.addWidget(self.counters, 1, 0, 1, 2) layout.addWidget(self.table_view, 1, 2, 1, 2) else: layout.addWidget(self.table_view, 1, 0, 1, 4) layout.addWidget(self.status_label, 2, 0) layout.addWidget(self.progress_bar, 2, 1, 1, 3) layout.setColumnStretch(2, 100) self.setLayout(layout) def set_model(self, model): self.model = model self.table_view.setModel(model) model.modelReset.connect(self._set_progress_maximum) model.modelReset.connect(self._update_status) if self._has_counters: model.modelReset.connect(self._update_counters) self._update_counters() model.progress_updated.connect(self._update_progress) self._set_progress_maximum() self._update_status() self.progress_bar.setVisible(model.has_background_callback) @property def selected_rows(self): return [self.model.data(index, Qt.UserRole) for index in self.table_view.selectionModel().selectedIndexes() if index.column() == 0] @property def search_text(self): return self.search_bar.text() def set_search_text(self, text): self.search_bar.blockSignals(True) self.search_bar.setText(text) self.search_bar.blockSignals(False) self.model.set_search_text(text) def _set_progress_maximum(self): self.progress_bar.setMaximum(self.model.progress_max) # do better ? def _update_progress(self, value): self.progress_bar.setValue(value) def _update_status(self): self.status_label.setText(STATUS_LABEL_MESSAGE.format( self.model.rowCount(), self.model.total_row_count )) def _counter_clicked(self, entry, checked_buttons): entries = [entry for entry, active in checked_buttons.items() if active] self.model.set_search_counters(entries) def _update_counters(self): self.counters.set(self.model.counters) def _auto_size_clicked(self): with Hourglass(): self.table_view.resizeColumnsToContents() def _double_clicked(self, index): row = self.model.data(index, Qt.UserRole) self.double_clicked.emit(row) def state(self): header_sizes = list() header = self.table_view.horizontalHeader() for section_index in range(header.count()): header_sizes.append(header.sectionSize(section_index)) return { 'header_sizes': header_sizes, 'search_text': self.search_bar.text() } def load_state(self, state): header = self.table_view.horizontalHeader() for section_index, size in enumerate(state['header_sizes']): header.resizeSection(section_index, size) self.search_bar.setText(state['search_text'])
class LibAdd(ToolInstance): help = "https://github.com/QChASM/SEQCROW/wiki/Add-to-Personal-Library-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.key_atomspec = [] self._build_ui() def _build_ui(self): layout = QGridLayout() library_tabs = QTabWidget() #ligand tab ligand_tab = QWidget() ligand_layout = QFormLayout(ligand_tab) self.ligand_name = QLineEdit() self.ligand_name.setText("") self.ligand_name.setPlaceholderText("leave blank to preview") self.ligand_name.setToolTip( "name of ligand you are adding to your ligand library\nleave blank to open a new model with just the ligand" ) ligand_layout.addRow("ligand name:", self.ligand_name) ligand_key_atoms = QPushButton("set key atoms to current selection") ligand_key_atoms.clicked.connect(self.update_key_atoms) ligand_key_atoms.setToolTip( "the current selection will be the key atoms for the ligand\nleave blank to automatically determine key atoms" ) ligand_layout.addRow(ligand_key_atoms) libadd_ligand = QPushButton("add current selection to library") libadd_ligand.clicked.connect(self.libadd_ligand) ligand_layout.addRow(libadd_ligand) #substituent tab sub_tab = QWidget() sub_layout = QFormLayout(sub_tab) self.sub_name = QLineEdit() self.sub_name.setText("") self.sub_name.setPlaceholderText("leave blank to preview") self.sub_name.setToolTip( "name of substituent you are adding to your substituent library\nleave blank to open a new model with just the substituent" ) sub_layout.addRow("substituent name:", self.sub_name) self.sub_confs = QSpinBox() self.sub_confs.setMinimum(1) sub_layout.addRow("number of conformers:", self.sub_confs) self.sub_angle = QSpinBox() self.sub_angle.setRange(0, 180) self.sub_angle.setSingleStep(30) sub_layout.addRow("angle between conformers:", self.sub_angle) libadd_sub = QPushButton("add current selection to library") libadd_sub.clicked.connect(self.libadd_substituent) sub_layout.addRow(libadd_sub) #ring tab ring_tab = QWidget() ring_layout = QFormLayout(ring_tab) self.ring_name = QLineEdit() self.ring_name.setText("") self.ring_name.setPlaceholderText("leave blank to preview") self.ring_name.setToolTip( "name of ring you are adding to your ring library\nleave blank to open a new model with just the ring" ) ring_layout.addRow("ring name:", self.ring_name) libadd_ring = QPushButton("add ring with selected walk to library") libadd_ring.clicked.connect(self.libadd_ring) ring_layout.addRow(libadd_ring) library_tabs.addTab(sub_tab, "substituent") library_tabs.addTab(ring_tab, "ring") library_tabs.addTab(ligand_tab, "ligand") self.library_tabs = library_tabs layout.addWidget(library_tabs) whats_this = QLabel() whats_this.setText( "<a href=\"req\" style=\"text-decoration: none;\">what's this?</a>" ) whats_this.setTextFormat(Qt.RichText) whats_this.setTextInteractionFlags(Qt.TextBrowserInteraction) whats_this.linkActivated.connect(self.open_link) whats_this.setToolTip( "click for more information about AaronTools libraries") layout.addWidget(whats_this) self.tool_window.ui_area.setLayout(layout) self.tool_window.manage(None) def update_key_atoms(self): selection = selected_atoms(self.session) if not selection.single_structure: raise RuntimeError("selected atoms must be on the same model") else: self.key_atomspec = selection self.tool_window.status("key atoms set to %s" % " ".join(atom.atomspec for atom in selection)) def libadd_ligand(self): """add ligand to library or open it in a new model""" selection = selected_atoms(self.session) if not selection.single_structure: raise RuntimeError("selected atoms must be on the same model") rescol = ResidueCollection(selection[0].structure) ligand_atoms = [ atom for atom in rescol.atoms if atom.chix_atom in selection ] key_chix_atoms = [ atom for atom in self.key_atomspec if not atom.deleted ] if len(key_chix_atoms) < 1: key_atoms = set([]) for atom in ligand_atoms: for atom2 in atom.connected: if atom2 not in ligand_atoms: key_atoms.add(atom) else: key_atoms = rescol.find( [AtomSpec(atom.atomspec) for atom in key_chix_atoms]) if len(key_atoms) < 1: raise RuntimeError("no key atoms could be determined") lig_name = self.ligand_name.text() ligand = Component(ligand_atoms, name=lig_name, key_atoms=key_atoms) ligand.comment = "K:%s" % ",".join( [str(ligand.atoms.index(atom) + 1) for atom in key_atoms]) if len(lig_name) == 0: chimerax_ligand = ResidueCollection(ligand).get_chimera( self.session) chimerax_ligand.name = "ligand preview" self.session.models.add([chimerax_ligand]) bild_obj = key_atom_highlight(ligand, [0.2, 0.5, 0.8, 0.5], self.session) self.session.models.add(bild_obj, parent=chimerax_ligand) else: check_aaronlib_dir() filename = os.path.join(AARONLIB, "Ligands", lig_name + ".xyz") if os.path.exists(filename): exists_warning = QMessageBox() exists_warning.setIcon(QMessageBox.Warning) exists_warning.setText( "%s already exists.\nWould you like to overwrite?" % filename) exists_warning.setStandardButtons(QMessageBox.Yes | QMessageBox.No) rv = exists_warning.exec_() if rv == QMessageBox.Yes: ligand.write(outfile=filename) self.tool_window.status("%s added to ligand library" % lig_name) else: self.tool_window.status( "%s has not been added to ligand library" % lig_name) else: ligand.write(outfile=filename) self.tool_window.status("%s added to ligand library" % lig_name) def libadd_ring(self): """add ring to library or open it in a new model""" selection = self.session.seqcrow_ordered_selection_manager.selection if not selection.single_structure: raise RuntimeError("selected atoms must be on the same model") rescol = ResidueCollection(selection[0].structure) walk_atoms = rescol.find( [AtomSpec(atom.atomspec) for atom in selection]) if len(walk_atoms) < 1: raise RuntimeError("no walk direction could be determined") ring_name = self.ring_name.text() ring = Ring(rescol, name=ring_name, end=walk_atoms) ring.comment = "E:%s" % ",".join( [str(rescol.atoms.index(atom) + 1) for atom in walk_atoms]) if len(ring_name) == 0: chimerax_ring = ResidueCollection(ring).get_chimera(self.session) chimerax_ring.name = "ring preview" self.session.models.add([chimerax_ring]) bild_obj = show_walk_highlight(ring, chimerax_ring, [0.9, 0.4, 0.3, 0.9], self.session) self.session.models.add(bild_obj, parent=chimerax_ring) else: check_aaronlib_dir() filename = os.path.join(AARONLIB, "Rings", ring_name + ".xyz") if os.path.exists(filename): exists_warning = QMessageBox() exists_warning.setIcon(QMessageBox.Warning) exists_warning.setText( "%s already exists.\nWould you like to overwrite?" % filename) exists_warning.setStandardButtons(QMessageBox.Yes | QMessageBox.No) rv = exists_warning.exec_() if rv == QMessageBox.Yes: ring.write(outfile=filename) self.tool_window.status("%s added to ring library" % ring_name) else: self.tool_window.status( "%s has not been added to ring library" % ring_name) else: ring.write(outfile=filename) self.tool_window.status("%s added to ring library" % ring_name) def libadd_substituent(self): """add ligand to library or open it in a new model""" selection = selected_atoms(self.session) if not selection.single_structure: raise RuntimeError("selected atoms must be on the same model") residues = [] for atom in selection: if atom.residue not in residues: residues.append(atom.residue) rescol = ResidueCollection(selection[0].structure, convert_residues=residues) substituent_atoms = [ atom for atom in rescol.atoms if atom.chix_atom in selection ] start = None avoid = None for atom in substituent_atoms: for atom2 in atom.connected: if atom2 not in substituent_atoms: if start is None: start = atom avoid = atom2 else: raise RuntimeError( "substituent must only have one connection to the molecule" ) if start is None: raise RuntimeError( "substituent is not connected to a larger molecule") substituent_atoms.remove(start) substituent_atoms.insert(0, start) sub_name = self.sub_name.text() confs = self.sub_confs.value() angle = self.sub_angle.value() comment = "CF:%i,%i" % (confs, angle) if len(sub_name) == 0: sub = Substituent(substituent_atoms, name="test", conf_num=confs, conf_angle=angle) else: sub = Substituent(substituent_atoms, name=sub_name, conf_num=confs, conf_angle=angle) sub.comment = comment #align substituent bond to x axis sub.coord_shift(-avoid.coords) x_axis = np.array([1., 0., 0.]) n = np.linalg.norm(start.coords) vb = start.coords / n d = np.linalg.norm(vb - x_axis) theta = np.arccos((d**2 - 2) / -2) vx = np.cross(vb, x_axis) sub.rotate(vx, theta) add = False if len(sub_name) == 0: chimerax_sub = ResidueCollection(sub).get_chimera(self.session) chimerax_sub.name = "substituent preview" self.session.models.add([chimerax_sub]) bild_obj = ghost_connection_highlight( sub, [0.60784, 0.145098, 0.70196, 0.5], self.session) self.session.models.add(bild_obj, parent=chimerax_sub) else: check_aaronlib_dir() filename = os.path.join(AARONLIB, "Subs", sub_name + ".xyz") if os.path.exists(filename): exists_warning = QMessageBox() exists_warning.setIcon(QMessageBox.Warning) exists_warning.setText( "%s already exists.\nWould you like to overwrite?" % filename) exists_warning.setStandardButtons(QMessageBox.Yes | QMessageBox.No) rv = exists_warning.exec_() if rv == QMessageBox.Yes: add = True else: self.tool_window.status( "%s has not been added to substituent library" % sub_name) else: add = True if add: sub.write(outfile=filename) self.tool_window.status("%s added to substituent library" % sub_name) register_selectors(self.session.logger, sub_name) if self.session.ui.is_gui: if (sub_name not in ELEMENTS and sub_name[0].isalpha() and (len(sub_name) > 1 and not any(not (c.isalnum() or c in "+-") for c in sub_name[1:]))): add_submenu = self.session.ui.main_window.add_select_submenu add_selector = self.session.ui.main_window.add_menu_selector substituent_menu = add_submenu(['Che&mistry'], 'Substituents') add_selector(substituent_menu, sub_name, sub_name) 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 open_link(self, *args): if self.library_tabs.currentIndex() == 0: link = "https://github.com/QChASM/AaronTools.py/wiki/AaronTools-Libraries#substituents" elif self.library_tabs.currentIndex() == 1: link = "https://github.com/QChASM/AaronTools.py/wiki/AaronTools-Libraries#rings" elif self.library_tabs.currentIndex() == 2: link = "https://github.com/QChASM/AaronTools.py/wiki/AaronTools-Libraries#ligands" run(self.session, "open %s" % link)
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()