Ejemplo n.º 1
0
    def __init__(self, element, *args, single_state=False, **kwargs):
        super().__init__(element, *args, **kwargs)

        self._tristate = False
        self._single_state = single_state

        self.setMinimumWidth(int(1.4*self.fontMetrics().boundingRect("QQ").width()))
        self.setMaximumWidth(int(1.4*self.fontMetrics().boundingRect("QQ").width()))
        self.setMinimumHeight(int(1.5*self.fontMetrics().boundingRect("QQ").height()))
        self.setMaximumHeight(int(1.5*self.fontMetrics().boundingRect("QQ").height()))
        
        self.ele_color = tuple(list(element_color(ELEMENTS.index(element)))[:-1])
        
        if not single_state:
            self.state = 0
            self.setStyleSheet("QPushButton { background: whitesmoke; color: DarkSlateGray; font-weight: normal; }")
            self.clicked.connect(self._changeState)
        else:
            self.state = 1
            #weird function to decide if text color is white or black based on jmol color
            #it's harder to see white on green, but easier to see white on blue
            style = "{ background: rgb(%i, %i, %i); " % self.ele_color + \
            "alternate-background-color: rgb(%i, %i, %i); " % self.ele_color + \
            "color: %s; font-weight: bold; }" % contrast_bw(self.ele_color) 

            self.setStyleSheet("QPushButton, QPushButton:down %s" % style)
Ejemplo n.º 2
0
def place_helium(structure, res_name, position=None):
    '''If position is None, place in the center of view'''
    max_existing = 0
    for r in structure.residues:
        if r.chain_id == "het" and r.number > max_existing:
            max_existing = r.number
    res = structure.new_residue(res_name, "het", max_existing + 1)
    if position is None:
        if len(structure.session.models) == 0:
            position = (0.0, 0.0, 0.0)
        else:
            #view = structure.session.view
            #n, f = view.near_far_distances(view.camera, None)
            #position = view.camera.position.origin() + (n+f) * view.camera.view_direction() / 2

            # apparently the commented-out code above is equivalent to...
            position = structure.session.main_view.center_of_rotation

    from numpy import array
    position = array(position)
    from chimerax.atomic.struct_edit import add_atom
    helium = Element.get_element("He")
    a = add_atom("He", helium, res, position)
    from chimerax.atomic.colors import element_color
    a.color = element_color(helium.number)
    a.draw_mode = a.BALL_STYLE
    return a
Ejemplo n.º 3
0
def seqcrow_bse(session, models=None, atoms=None):
    """non-H atoms are displayed with B&S
    H atoms are displayed with S
    atoms colored by Jmol colors"""

    from AaronTools.const import RADII
    from chimerax.atomic import AtomicStructure, Atom, Bond
    from chimerax.atomic.colors import element_color

    if models is None or atoms is None:
        apply_seqcrow_bse_lighting(session)

    if models is None:
        if atoms is None:
            models = session.models.list(type=AtomicStructure)
        else:
            models = list(set([atom.structure for atom in atoms]))
    elif isinstance(models, AtomicStructure):
        models = [models]

    for m in models:
        if atoms is None:
            m.ball_scale = 0.4

        if atoms is None:
            atom_list = m.atoms
        else:
            atom_list = [
                atom for atom in atoms
                if (atom.structure is m or atom.residue is m)
            ]

        for bond in m.bonds:
            if any(a in atom_list for a in bond.atoms):
                bond.halfbond = True
                bond.radius = 0.16
                bond.hide = False

        for atom in atom_list:
            ele = atom.element.name
            color = element_color(atom.element.number)

            atom.color = color
            atom.display = True

            if ele in RADII:
                #AaronTools has bonding radii, maybe I should use vdw?
                #check to see how necessary this is
                atom.radius = RADII[ele]

            if ele != 'H':
                atom.draw_mode = Atom.BALL_STYLE
            elif len(atom.neighbors) > 1:
                atom.radius = 0.625
                atom.draw_mode = Atom.BALL_STYLE
            else:
                atom.draw_mode = Atom.STICK_STYLE
Ejemplo n.º 4
0
def complete_terminal_carboxylate(session, cter):
    from chimerax.atomic.bond_geom import bond_positions
    from chimerax.atomic.struct_edit import add_atom
    from chimerax.atomic import Element
    if cter.find_atom("OXT"):
        return
    c = cter.find_atom("C")
    if c:
        if c.num_bonds != 2:
            return
        loc = bond_positions(c.coord, 3, 1.229,
                             [n.coord for n in c.neighbors])[0]
        oxt = add_atom("OXT", Element.get_element("O"), cter, loc, bonded_to=c)
        from chimerax.atomic.colors import element_color
        if c.color == element_color(c.element.number):
            oxt.color = element_color(oxt.element.number)
        else:
            oxt.color = c.color
        session.logger.info("Missing OXT added to C-terminal residue %s" %
                            str(cter))
Ejemplo n.º 5
0
def determine_h_color(parent_atom):
    global _h_coloring, _solvent_atoms
    res = parent_atom.residue
    struct = parent_atom.structure
    if struct not in _solvent_atoms:
        from weakref import WeakSet
        solvent_set = WeakSet()
        struct_atoms = struct.atoms
        solvent_set.update([
            a for a in struct_atoms.filter(
                struct_atoms.structure_categories == "solvent")
        ])
        _solvent_atoms[struct] = solvent_set
    else:
        solvent_set = _solvent_atoms[struct]
    if res.name in res.water_res_names or parent_atom in solvent_set:
        return element_color(1)
    if parent_atom.structure in _h_coloring:
        color_scheme = _h_coloring[parent_atom.structure]
    else:
        num_match_elements = 0
        for a in parent_atom.structure.atoms:
            if a.residue.name in res.water_res_names or struct in solvent_set:
                continue
            if a.element.name == "C":
                continue
            if a.color == element_color(a.element.number):
                num_match_elements += 1
                if num_match_elements > 1:
                    color_scheme = "element"
                    break
            else:
                color_scheme = "parent"
                break
        else:
            color_scheme = "element"
        _h_coloring[parent_atom.structure] = color_scheme
    return parent_atom.color if color_scheme == "parent" else element_color(1)
Ejemplo n.º 6
0
def cmd_centroid(session, atoms=None, *, mass_weighting=False, name="centroid", color=None, radius=2.0):
    """Wrapper to be called by command line.

       Use chimerax.centroids.centroid for other programming applications.
    """
    from chimerax.core.errors import UserError

    from chimerax.atomic import AtomicStructure, concatenate, Structure
    if atoms is None:
        structures_atoms = [m.atoms for m in session.models if isinstance(m, AtomicStructure)]
        if structures_atoms:
            atoms = concatenate(structures_atoms)
        else:
            raise UserError("Atom specifier selects no atoms")

    structures = atoms.unique_structures
    if len(structures) > 1:
        crds = atoms.scene_coords
    else:
        crds = atoms.coords
    if mass_weighting:
        masses = atoms.elements.masses
        avg_mass = masses.sum() / len(masses)
        import numpy
        weights = masses[:, numpy.newaxis] / avg_mass
    else:
        weights = None
    xyz = centroid(crds, weights=weights)
    s = Structure(session, name=name)
    r = s.new_residue('centroid', 'centroid', 1)
    from chimerax.atomic.struct_edit import add_atom
    a = add_atom('cent', 'C', r, xyz)
    if color:
        a.color = color.uint8x4()
    else:
        from chimerax.atomic.colors import element_color, predominant_color
        color = predominant_color(atoms)
        if color is None:
            a.color = element_color(a.element.number)
        else:
            a.color = color
    a.radius = radius
    if len(structures) > 1:
        session.models.add([s])
    else:
        structures[0].add([s])

    session.logger.info("Centroid '%s' placed at %s" % (name, xyz))
    return a
Ejemplo n.º 7
0
 def element_changed(self, *args):
     elements = self.ptable.selectedElements()
     
     if len(elements) == 1:
         element = elements[0]
         self.button.setText(element)
         ele_color = tuple(list(element_color(ELEMENTS.index(element)))[:-1])
         self.button.setStyleSheet(
             "QPushButton { background: rgb(%i, %i, %i); color: %s; font-weight: bold; }" % (
                 *ele_color,
                 'white' if sum(
                     int(x < 130) - int(x > 225) for x in ele_color
                 ) - int(ele_color[1] > 225) +
                 int(ele_color[2] > 200) >= 2 else 'black'
             )
         )
Ejemplo n.º 8
0
def seqcrow_vdw(session, models=None, atoms=None):
    """atoms are displayed as B
    atoms colored by Jmol colors"""

    from AaronTools.const import VDW_RADII
    from chimerax.atomic import AtomicStructure, Atom
    from chimerax.atomic.colors import element_color

    if models is None or atoms is None:
        apply_seqcrow_bse_lighting(session)

    if models is None:
        if atoms is None:
            models = session.models.list(type=AtomicStructure)
        else:
            models = list(set([atom.structure for atom in atoms]))
    elif isinstance(models, AtomicStructure):
        models = [models]

    for m in models:
        if atoms is None:
            m.ball_scale = 1.0

        if atoms is None:
            atom_list = m.atoms
        else:
            atom_list = [atom for atom in atoms if atom.structure is m]

        for bond in m.bonds:
            if any(a in atom_list for a in bond.atoms):
                bond.halfbond = True
                bond.radius = 0.16
                bond.hide = False

        for atom in atom_list:
            ele = atom.element.name
            color = element_color(atom.element.number)

            atom.color = color
            atom.display = True

            if ele in VDW_RADII:
                atom.radius = VDW_RADII[ele]

            atom.draw_mode = Atom.BALL_STYLE
Ejemplo n.º 9
0
    def __init__(self, element, *args, **kwargs):
        super().__init__(element, *args, **kwargs)

        self._tristate = False

        self.state = 0
        self.setMinimumWidth(
            int(1.3 * self.fontMetrics().boundingRect("QQ").width()))
        self.setMaximumWidth(
            int(1.3 * self.fontMetrics().boundingRect("QQ").width()))
        self.setMinimumHeight(
            int(1.5 * self.fontMetrics().boundingRect("QQ").height()))
        self.setMaximumHeight(
            int(1.5 * self.fontMetrics().boundingRect("QQ").height()))

        self.ele_color = tuple(
            list(element_color(ELEMENTS.index(element)))[:-1])
        self.setStyleSheet(
            "QPushButton { background: ghostwhite; color: lightgray; font-weight: normal; }"
        )

        self.clicked.connect(self._changeState)
Ejemplo n.º 10
0
def seqcrow_s(session, models=None, atoms=None):
    """atoms are represented with sticks
    atoms colored by Jmol colors"""

    from AaronTools.const import RADII
    from chimerax.atomic import AtomicStructure, Atom
    from chimerax.atomic.colors import element_color
    from SEQCROW.selectors import get_fragment

    if models is None or atoms is None:
        apply_seqcrow_s_lighting(session)

    if models is None:
        if atoms is None:
            models = session.models.list(type=AtomicStructure)
        else:
            models = list(set([atom.structure for atom in atoms]))
    elif isinstance(models, AtomicStructure):
        models = [models]

    for m in models:
        if atoms is None:
            m.ball_scale = 0.625

        if atoms is None:
            atom_list = m.atoms
        else:
            atom_list = [atom for atom in atoms if atom.structure is m]

        for bond in m.bonds:
            if any(a in atom_list for a in bond.atoms):
                bond.halfbond = True
                bond.radius = 0.25
                bond.hide = False

        tm_bonds = m.pseudobond_group(m.PBG_METAL_COORDINATION,
                                      create_type=None)
        ts_bonds = m.pseudobond_group("TS bonds", create_type=None)
        h_bonds = m.pseudobond_group("hydrogen bonds", create_type=None)

        for atom in atom_list:
            ele = atom.element.name
            color = element_color(atom.element.number)

            atom.color = color

            if not atom.neighbors:
                atom.draw_mode = Atom.BALL_STYLE
                if ele in RADII:
                    atom.radius = RADII[ele]

            else:
                atom.draw_mode = Atom.STICK_STYLE

            if atom.element.name == "H":
                display = len(atom.neighbors) != 1
                if tm_bonds:
                    if any(atom in bond.atoms
                           for bond in tm_bonds.pseudobonds):
                        display = True

                if h_bonds:
                    if any(atom in bond.atoms for bond in h_bonds.pseudobonds):
                        display = True

                if ts_bonds:
                    if any(atom in bond.atoms
                           for bond in ts_bonds.pseudobonds):
                        display = True

                if not display:
                    for bonded_atom in atom.neighbors:
                        if "C" != bonded_atom.element.name or ((
                                bonded_atom.element.name == "C" and
                            (
                                # show H's on terminal carbons that aren't RCH3's
                                sum(
                                    len(a.neighbors) == 1
                                    for a in bonded_atom.neighbors
                                ) >= bonded_atom.num_bonds - 1
                                and not (sum(
                                    int(a.element.name == "H")
                                    for a in bonded_atom.neighbors) == 3
                                         and len(bonded_atom.neighbors) == 4)
                                # show H's in TS bonds or on atoms that are coordinated to a metal
                                or
                                (ts_bonds
                                 and any(bonded_atom in bond.atoms
                                         for bond in ts_bonds.pseudobonds)) or
                                (tm_bonds
                                 and any(bonded_atom in bond.atoms
                                         for bond in tm_bonds.pseudobonds))))
                                                               # show H's that are on chiral carbons
                                                               # this is a really lazy check and doesn't get everything
                                                               or (sum(
                                                                   a.element.
                                                                   name == "H"
                                                                   for a in
                                                                   bonded_atom.
                                                                   neighbors
                                                               ) == 1 and (len(
                                                                   set(
                                                                       sum(
                                                                           get_fragment(
                                                                               a,
                                                                               bonded_atom
                                                                           ).
                                                                           elements
                                                                           .masses
                                                                       )
                                                                       for a in
                                                                       bonded_atom
                                                                       .neighbors
                                                                   )
                                                               ) == 4  # or any(a.element.name not in "CH" for a in bonded_atom.neighbors)
                                                                           )
                                                                   and
                                                                   bonded_atom.
                                                                   num_bonds
                                                                   == 4)):
                            display = True
                            break

                        # show H's on trigonal carbons adjacent to terminal trigonal carbons
                        if "C" == bonded_atom.element.name and bonded_atom.num_bonds < 4:
                            for bonded_atom2 in bonded_atom.neighbors:
                                if bonded_atom2.element.name == "C" and (sum(
                                        len(a.neighbors) == 1
                                        for a in bonded_atom2.neighbors
                                ) >= bonded_atom2.num_bonds - 1 and not (sum(
                                        int(a.element.name == "H")
                                        for a in bonded_atom2.neighbors
                                ) == 3 and len(bonded_atom2.neighbors) == 4)):
                                    display = True
                                    break

                atom.display = display
Ejemplo n.º 11
0
 def changeElement(self, element):
     self.ele_color = tuple(list(element_color(ELEMENTS.index(element)))[:-1])
     self.setText(element)
     self._changeState(state=self.state)
Ejemplo n.º 12
0
def seqcrow_s(session, models=None, atoms=None):
    """atoms are represented with sticks
    atoms colored by Jmol colors"""

    from AaronTools.const import RADII, VDW_RADII, TMETAL
    from chimerax.atomic import AtomicStructure, Atom, Bond
    from chimerax.atomic.colors import element_color

    if models is None or atoms is None:
        apply_seqcrow_s_lighting(session)

    if models is None:
        if atoms is None:
            models = session.models.list(type=AtomicStructure)
        else:
            models = list(set([atom.structure for atom in atoms]))
    elif isinstance(models, AtomicStructure):
        models = [models]

    for m in models:
        if atoms is None:
            m.ball_scale = 0.625

        if atoms is None:
            atom_list = m.atoms
        else:
            atom_list = [atom for atom in atoms if atom.structure is m]

        for bond in m.bonds:
            if any(a in atom_list for a in bond.atoms):
                bond.halfbond = True
                bond.radius = 0.25
                bond.hide = False

        tm_bonds = m.pseudobond_group(m.PBG_METAL_COORDINATION,
                                      create_type=None)
        ts_bonds = m.pseudobond_group("TS bonds", create_type=None)
        h_bonds = m.pseudobond_group("hydrogen bonds", create_type=None)

        for atom in atom_list:
            ele = atom.element.name
            color = element_color(atom.element.number)

            atom.color = color

            if not atom.neighbors:
                atom.draw_mode = Atom.BALL_STYLE
                if ele in RADII:
                    atom.radius = RADII[ele]

            else:
                atom.draw_mode = Atom.STICK_STYLE

            if atom.element.name == "H":
                display = len(atom.neighbors) != 1
                if tm_bonds:
                    if any(atom in bond.atoms
                           for bond in tm_bonds.pseudobonds):
                        display = True

                if h_bonds:
                    if any(atom in bond.atoms for bond in h_bonds.pseudobonds):
                        display = True

                if ts_bonds:
                    if any(atom in bond.atoms
                           for bond in ts_bonds.pseudobonds):
                        display = True

                if not display:
                    for bonded_atom in atom.neighbors:
                        # do not display H's on C unless it's a terminus
                        if "C" != bonded_atom.element.name or (
                            (bonded_atom.element.name == "C" and
                             (sum(
                                 len(a.neighbors) == 1
                                 for a in bonded_atom.neighbors) >=
                              bonded_atom.num_bonds - 1 and not (sum(
                                  int(a.element.name == "H")
                                  for a in bonded_atom.neighbors) == 3 and len(
                                      bonded_atom.neighbors) == 4) or
                              (ts_bonds
                               and any(bonded_atom in bond.atoms
                                       for bond in ts_bonds.pseudobonds)) or
                              (tm_bonds
                               and any(bonded_atom in bond.atoms
                                       for bond in tm_bonds.pseudobonds))))):
                            display = True
                            break

                atom.display = display
Ejemplo n.º 13
0
    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 = QPushButton("C")
        self.ele1.setMinimumWidth(
            int(1.3 * self.ele1.fontMetrics().boundingRect("QQ").width()))
        self.ele1.setMaximumWidth(
            int(1.3 * self.ele1.fontMetrics().boundingRect("QQ").width()))
        self.ele1.setMinimumHeight(
            int(1.5 * self.ele1.fontMetrics().boundingRect("QQ").height()))
        self.ele1.setMaximumHeight(
            int(1.5 * self.ele1.fontMetrics().boundingRect("QQ").height()))
        self.ele1.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
        ele_color = tuple(list(element_color(ELEMENTS.index("C")))[:-1])
        self.ele1.setStyleSheet(
            "QPushButton { background: rgb(%i, %i, %i); color: %s; font-weight: bold; }"
            % (*ele_color,
               'white' if sum(int(x < 130) - int(x > 225)
                              for x in ele_color) - int(ele_color[1] > 225) +
               int(ele_color[2] > 200) >= 2 else 'black'))
        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 = QPushButton("C")
        self.ele2.setMinimumWidth(
            int(1.3 * self.ele2.fontMetrics().boundingRect("QQ").width()))
        self.ele2.setMaximumWidth(
            int(1.3 * self.ele2.fontMetrics().boundingRect("QQ").width()))
        self.ele2.setMinimumHeight(
            int(1.5 * self.ele2.fontMetrics().boundingRect("QQ").height()))
        self.ele2.setMaximumHeight(
            int(1.5 * self.ele2.fontMetrics().boundingRect("QQ").height()))
        self.ele2.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
        ele_color = tuple(list(element_color(ELEMENTS.index("C")))[:-1])
        self.ele2.setStyleSheet(
            "QPushButton { background: rgb(%i, %i, %i); color: %s; font-weight: bold; }"
            % (*ele_color,
               'white' if sum(int(x < 130) - int(x > 225)
                              for x in ele_color) - int(ele_color[1] > 225) +
               int(ele_color[2] > 200) >= 2 else 'black'))
        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)
Ejemplo n.º 14
0
def use_rotamer(session, res, rots, retain=False, log=False, bfactor=None):
    """Takes a Residue instance and either a list or dictionary of rotamers (as returned by get_rotamers,
       i.e. with backbone already matched) and swaps the Residue's side chain with the given rotamers.

       If the rotamers are a dictionary, then the keys should match the alt locs of the CA atom, and
       the corresponding rotamer will be used for that alt loc.  If the alt locs are a list, if the list
       has only one rotamer then that rotamer will be used for each CA alt loc.  If the list has multiple
       rotamers, then the CA must have only one alt loc (namely ' ') and all the rotamers will be attached,
       using different alt loc characters for each.

       If 'retain' is True, existing side chains will be retained.  If 'bfactor' is None, then the
       current highest existing bfactor in the residue will be used.
    """
    N = res.find_atom("N")
    CA = res.find_atom("CA")
    C = res.find_atom("C")
    if not N or not C or not CA:
        raise LimitationError(
            "N, CA, or C missing from %s: needed for side-chain pruning algorithm"
            % res)
    import string
    alt_locs = string.ascii_uppercase + string.ascii_lowercase + string.digits + string.punctuation
    if retain and CA.alt_locs:
        raise LimitationError(
            "Cannot retain side chains if multiple CA alt locs")
    ca_alt_locs = [' '] if not CA.alt_locs else CA.alt_locs
    if not isinstance(rots, dict):
        # reformat as dictionary
        if CA.alt_locs and len(rots) > 1:
            raise LimitationError(
                "Cannot add multiple rotamers to multi-position backbone")
        retained_alt_locs = side_chain_locs(res) if retain else []
        num_retained = len(retained_alt_locs)
        if len(rots) + num_retained > len(alt_locs):
            raise LimitationError("Don't have enough unique alternate "
                                  "location characters to place %d rotamers." %
                                  len(rots))
        if len(rots) + num_retained > 1:
            rots = {
                loc: rot
                for loc, rot in zip(
                    [c for c in alt_locs
                     if c not in retained_alt_locs][:len(rots)], rots)
            }
        else:
            rots = {alt_loc: rots[0] for alt_loc in ca_alt_locs}
    swap_type = list(rots.values())[0].residues[0].name
    if retain and res.name != swap_type:
        raise LimitationError(
            "Cannot retain side chains if rotamers are a different residue type"
        )
    rot_anchors = {}
    for rot in rots.values():
        rot_res = rot.residues[0]
        rot_N, rot_CA = rot_res.find_atom("N"), rot_res.find_atom("CA")
        if not rot_N or not rot_CA:
            raise LimitationError(
                "N or CA missing from rotamer: cannot matchup with original residue"
            )
        rot_anchors[rot] = (rot_N, rot_CA)
    color_by_element = N.color != CA.color
    if color_by_element:
        carbon_color = CA.color
    else:
        uniform_color = N.color
    # prune old side chain
    bfactor = bfactor_for_res(res, bfactor)
    if not retain:
        res_atoms = res.atoms
        side_atoms = res_atoms.filter(res_atoms.is_side_onlys)
        serials = {a.name: a.serial_number for a in side_atoms}
        side_atoms.delete()
    else:
        serials = {}
    # for proline, also prune amide hydrogens
    if swap_type == "PRO":
        for nnb in N.neighbors[:]:
            if nnb.element.number == 1:
                N.structure.delete_atom(nnb)

    tot_prob = sum([r.rotamer_prob for r in rots.values()])
    with CA.suppress_alt_loc_change_notifications():
        res.name = swap_type
        from chimerax.atomic.struct_edit import add_atom, add_bond
        for alt_loc, rot in rots.items():
            if CA.alt_locs:
                CA.alt_loc = alt_loc
            if log:
                extra = " using alt loc %s" % alt_loc if alt_loc != ' ' else ""
                session.logger.info(
                    "Applying %s rotamer (chi angles: %s) to %s%s" %
                    (rot_res.name, " ".join(["%.1f" % c
                                             for c in rot.chis]), res, extra))
            # add new side chain
            rot_N, rot_CA = rot_anchors[rot]
            visited = set([N, CA, C])
            sprouts = [rot_CA]
            while sprouts:
                sprout = sprouts.pop()
                built_sprout = res.find_atom(sprout.name)
                for nb in sprout.neighbors:
                    built_nb = res.find_atom(nb.name)
                    if tot_prob == 0.0:
                        # some rotamers in Dunbrack are zero prob!
                        occupancy = 1.0 / len(rots)
                    else:
                        occupancy = rot.rotamer_prob / tot_prob
                    if not built_nb:
                        serial = serials.get(nb.name, None)
                        built_nb = add_atom(nb.name,
                                            nb.element,
                                            res,
                                            nb.coord,
                                            serial_number=serial,
                                            bonded_to=built_sprout,
                                            alt_loc=alt_loc)
                        built_nb.occupancy = occupancy
                        built_nb.bfactor = bfactor
                        if color_by_element:
                            if built_nb.element.name == "C":
                                built_nb.color = carbon_color
                            else:
                                from chimerax.atomic.colors import element_color
                                built_nb.color = element_color(
                                    built_nb.element.number)
                        else:
                            built_nb.color = uniform_color
                    elif built_nb not in visited:
                        built_nb.set_alt_loc(alt_loc, True)
                        built_nb.coord = nb.coord
                        built_nb.occupancy = occupancy
                        built_nb.bfactor = bfactor
                    if built_nb not in visited:
                        sprouts.append(nb)
                        visited.add(built_nb)
                    if built_nb not in built_sprout.neighbors:
                        add_bond(built_sprout, built_nb)
Ejemplo n.º 15
0
def template_swap_res(res, res_type, *, preserve=False, bfactor=None):
    """change 'res' into type 'res_type'"""

    fixed, buds, start, end = get_res_info(res)

    if res_type == "HIS":
        res_type = "HIP"
    if res_type in ["A", "C", "G", "T"
                    ] and res.name in ["DA", "DC", "DT", "DG"]:
        res_type = "D" + res_type
    from chimerax.atomic import TmplResidue, Atom
    tmpl_res = TmplResidue.get_template(res_type, start=start, end=end)
    if not tmpl_res:
        raise TemplateError("No connectivity template for residue '%s'" %
                            res_type)
    # sanity check:  does the template have the bud atoms?
    for bud in buds:
        if tmpl_res.find_atom(bud) is None:
            raise TemplateError("New residue type (%s) not compatible with"
                                " starting residue type (%s)" %
                                (res_type, res.name))
    color_by_element = False
    uniform_color = res.find_atom(buds[0]).color
    het = res.find_atom("N") or res.find_atom("O4'")
    if het:
        carbon = res.find_atom("CA") or res.find_atom("C4'")
        if carbon:
            color_by_element = het.color != carbon.color
            if color_by_element:
                carbon_color = carbon.color
            else:
                uniform_color = het.color

    bfactor = bfactor_for_res(res, bfactor)

    if preserve:
        if "CA" in fixed and res_type not in ['GLY', 'ALA']:
            raise TemplateSwapError(
                "'preserve' keyword not yet implemented for amino acids")
        a1 = res.find_atom("O4'")
        a2 = res.find_atom("C1'")
        if not a1 or not a2:
            preserve_pos = None
        else:
            dihed_names = {"N9": ["C4", "C8"], "N1": ["C2", "C6"]}
            a3 = res.find_atom("N9") or res.find_atom("N1")
            if a3:
                if a2 not in a3.neighbors:
                    preserve_pos = None
                else:
                    preserve_pos = a3.coord
            else:
                preserve_pos = None
        if preserve_pos:
            p1, p2, p3 = [a.coord for a in (a1, a2, a3)]
            preserved_pos = False
            prev_name, alt_name = dihed_names[a3.name]
            a4 = res.find_atom(prev_name)
            if a4 and a3 in a4.neighbors:
                p4 = a4.coord
                from chimerax.geometry import dihedral
                preserve_dihed = dihedral(p1, p2, p3, p4)
            else:
                preserve_dihed = None
        else:
            preserve_dihed = None

    # prune non-backbone atoms
    for a in res.atoms:
        if a.name not in fixed:
            a.structure.delete_atom(a)

    # add new sidechain
    new_atoms = []
    xf = None
    from chimerax.atomic.struct_edit import add_bond
    while len(buds) > 0:
        bud = buds.pop()
        tmpl_bud = tmpl_res.find_atom(bud)
        res_bud = res.find_atom(bud)

        try:
            info = Atom.idatm_info_map[tmpl_bud.idatm_type]
            geom = info.geometry
            subs = info.substituents
        except KeyError:
            raise AssertionError(
                "Can't determine atom type information for atom %s of residue %s"
                % (bud, res))

        # use .coord rather than .scene_coord:  we want to set the new atom's coord,
        # to which the proper xform will then be applied
        for a, b in zip(tmpl_bud.neighbors, tmpl_bud.bonds):
            if a.element.number == 1:
                # don't add hydrogens
                continue
            if res.find_atom(a.name):
                res_bonder = res.find_atom(a.name)
                if res_bonder not in res_bud.neighbors:
                    add_bond(a, res_bonder)
                continue

            new_atom = None
            num_bonded = len(res_bud.bonds)
            if num_bonded >= subs:
                raise AssertionError(
                    "Too many atoms bonded to %s of residue %s" % (bud, res))
            if num_bonded == 0:
                raise AssertionError(
                    "Atom %s of residue %s has no neighbors after pruning?!?" %
                    (bud, res))
            # since fused ring systems may have distorted bond angles, always use dihedral placement
            real1 = res_bud.neighbors[0]
            kw = {}
            if preserve:
                if preserve_pos and not preserved_pos:
                    kw['pos'] = preserve_pos
                    preserved_pos = True
                    preserved_name = a.name
                elif preserve_dihed is not None:
                    prev_name, alt_name = dihed_names[preserved_name]
                    if a.name == prev_name:
                        kw['dihed'] = preserve_dihed
                    elif a.name == alt_name:
                        kw['dihed'] = preserve_dihed + 180.0
            if not kw and xf is not None:
                kw['pos'] = xf * a.coord

            new_atom = form_dihedral(res_bud, real1, tmpl_res, a, b, **kw)
            new_atom.draw_mode = res_bud.draw_mode
            new_atom.bfactor = bfactor
            if color_by_element:
                if new_atom.element.name == "C":
                    new_atom.color = carbon_color
                else:
                    from chimerax.atomic.colors import element_color
                    new_atom.color = element_color(new_atom.element.number)
            else:
                new_atom.color = uniform_color
            new_atoms.append(new_atom)

            for bonded in a.neighbors:
                bond_atom = res.find_atom(bonded.name)
                if not bond_atom:
                    continue
                add_bond(new_atom, bond_atom)
            buds.append(new_atom.name)

        # once we've placed 3 side chain atoms, we use superpositioning to
        # place the remainder of the side chain, since dihedrals will
        # likely distort ring closures if 'preserve' is true
        if buds and not xf and len(new_atoms) >= 3:
            placed_positions = []
            tmpl_positions = []
            for na in new_atoms:
                placed_positions.append(na.coord)
                tmpl_positions.append(tmpl_res.find_atom(na.name).coord)
            import numpy
            from chimerax.geometry import align_points
            xf = align_points(numpy.array(tmpl_positions),
                              numpy.array(placed_positions))[0]

    res.name = res_type
Ejemplo n.º 16
0
def cmd_define_plane(session,
                     atoms,
                     *,
                     thickness=defaults["plane_thickness"],
                     padding=0.0,
                     color=None,
                     radius=None,
                     name="plane"):
    """Wrapper to be called by command line.

       Use chimerax.axes_planes.plane for other programming applications.
    """
    from chimerax.core.errors import UserError

    from chimerax.atomic import AtomicStructure, concatenate, Structure
    if atoms is None:
        structures_atoms = [
            m.atoms for m in session.models if isinstance(m, AtomicStructure)
        ]
        if structures_atoms:
            atoms = concatenate(structures_atoms)
        else:
            raise UserError("Atom specifier selects no atoms")
    if len(atoms) < 3:
        raise UserError("Must specify at least 3 atoms to define a plane")

    structures = atoms.unique_structures
    if len(structures) > 1:
        crds = atoms.scene_coords
    else:
        crds = atoms.coords

    from chimerax.geometry import Plane, distance_squared
    plane = Plane(crds)

    if radius is None:
        max_sq_dist = None
        origin = plane.origin
        for crd in crds:
            projected = plane.nearest(crd)
            sq_dist = distance_squared(origin, projected)
            if max_sq_dist is None or sq_dist > max_sq_dist:
                max_sq_dist = sq_dist
        from math import sqrt
        radius = sqrt(max_sq_dist)

    if color is None:
        from chimerax.atomic.colors import element_color, predominant_color
        color = predominant_color(atoms)
        if color is None:
            color = element_color(a.element.number)
    else:
        color = color.uint8x4()

    plane_model = PlaneModel(session, name, plane, thickness, radius + padding,
                             color)
    if len(structures) > 1:
        session.models.add([plane_model])
    else:
        structures[0].add([plane_model])
    session.logger.info("Plane %s' placed at %s with normal %s" %
                        (name, plane.origin, plane.normal))
    return plane_model
Ejemplo n.º 17
0
    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 = QPushButton("C")
        self.element.setMinimumWidth(int(1.3*self.element.fontMetrics().boundingRect("QQ").width()))
        self.element.setMaximumWidth(int(1.3*self.element.fontMetrics().boundingRect("QQ").width()))
        self.element.setMinimumHeight(int(1.5*self.element.fontMetrics().boundingRect("QQ").height()))
        self.element.setMaximumHeight(int(1.5*self.element.fontMetrics().boundingRect("QQ").height()))
        ele_color = tuple(list(element_color(ELEMENTS.index("C")))[:-1])
        self.element.setStyleSheet(
            "QPushButton { background: rgb(%i, %i, %i); color: %s; font-weight: bold; }" % (
                *ele_color,
                'white' if sum(
                    int(x < 130) - int(x > 225) for x in ele_color
                ) - int(ele_color[1] > 225) +
                int(ele_color[2] > 200) >= 2 else 'black'
            )
        )
        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)