Example #1
0
def save_structure(session, file, models, xforms, used_data_names, selected_only, displayed_only, fixed_width, best_guess):
    # save mmCIF data section for a structure
    # 'models' should only have more than one model if NMR ensemble
    # All 'models' should have the same metadata.
    # All 'models' should have the same number of atoms, but in PDB files
    # then often don't, so pick the model with the most atoms.
    #
    from chimerax.atomic import concatenate, Atoms
    if len(models) == 1:
        best_m = models[0]
    else:
        # TODO: validate that the models are actually similar, ie.,
        # same number of chains which same chain ids and sequences,
        # and same kinds of HET residues
        tmp = list(models)
        tmp.sort(key=lambda m: m.num_atoms)
        best_m = tmp[-1]
    name = ''.join(best_m.name.split())  # Remove all whitespace from name
    name = name.encode('ascii', errors='ignore').decode('ascii')  # Drop non-ascii characters
    if name in used_data_names:
        count = 0
        while True:
            count += 1
            n = '%s_%d' % (name, count)
            if n not in used_data_names:
                break
        name = n
    used_data_names.add(name)

    restrict = None
    if selected_only:
        atoms_list = session.selection.items("atoms")
        if not atoms_list:
            if not session.in_script:
                from chimerax.core.errors import UserError
                raise UserError("No atoms selected")
            session.logger.warning("No atoms selected")
            restrict = Atoms()
        else:
            restrict = concatenate(atoms_list)
            restrict = restrict.filter([st in models for st in restrict.structures])
    if displayed_only:
        displayed_atoms = concatenate([m.atoms.filter(m.atoms.displays) for m in models])
        if restrict is None:
            restrict = displayed_atoms
        else:
            restrict = restrict.intersect(displayed_atoms)
    if restrict is not None:
        restrict_residues = restrict.unique_residues
        restrict_chains = restrict_residues.unique_chains

    def nonblank_chars(name):
        return ''.join(ch for ch in name if not ch.isspace())

    print('data_%s' % nonblank_chars(name), file=file)
    print('#', file=file)

    best_metadata = best_m.metadata  # get once from C++ layer

    _save_metadata(best_m, ['entry'], file, best_metadata)

    ChimeraX_audit_conform.print(file=file, fixed_width=fixed_width)

    audit_syntax_info = ChimeraX_audit_syntax_info.copy()
    if not fixed_width:
        audit_syntax_info["fixed_width"] = ""
    tags, data = zip(*audit_syntax_info.items())
    audit_syntax = mmcif.CIFTable("audit_syntax", tags, data)
    audit_syntax.print(file=file, fixed_width=fixed_width)

    from .mmcif import _add_citation, _add_software

    citation, citation_author, citation_editor = _add_citation(
            best_m, ChimeraX_citation_id, ChimeraX_citation_info,
            ChimeraX_authors, metadata=best_metadata, return_existing=True)
    software = _add_software(
            best_m, ChimeraX_software_info['name'], ChimeraX_software_info,
            metadata=best_metadata, return_existing=True)
    citation.print(file, fixed_width=fixed_width)
    citation_author.print(file, fixed_width=fixed_width)
    if citation_editor is not None:
        citation_editor.print(file, fixed_width=fixed_width)
    software.print(file, fixed_width=fixed_width)
    del citation, citation_author, citation_editor, software

    save_components(best_m, file, best_metadata, fixed_width)

    _save_metadata(best_m, ['exptl'], file, best_metadata)

    from chimerax.atomic import Residue
    old_entity, old_asym = mmcif.get_mmcif_tables_from_metadata(
        best_m, ['entity', 'struct_asym'], metadata=best_metadata)
    try:
        if not old_entity or not old_asym:
            raise ValueError
        old_mmcif_chain_to_entity = old_asym.mapping('id', 'entity_id')
        old_entity_to_description = old_entity.mapping('id', 'pdbx_description')
    except ValueError:
        old_mmcif_chain_to_entity = {}
        old_entity_to_description = {}

    from collections import OrderedDict
    entity_info = {}     # { entity_id: (type, pdbx_description) }
    asym_info = {}       # { auth_chain_id: (entity_id, label_asym_id) }
    het_asym_info = {}   # { mmcif_chain_id: (entity_id, label_asym_id) }
    poly_info = []       # [(entity_id, type, one-letter-seq)]
    poly_seq_info = []   # [(entity_id, num, mon_id)]
    pdbx_poly_info = []  # [(entity_id, asym_id, mon_id, seq_id, pdb_strand_id, auth_seq_num, pdb_ins_code)]
    residue_info = {}    # { residue: (label_asym_id, label_seq_id) }

    skipped_sequence_info = False

    seq_entities = OrderedDict()   # { chain.characters : (entity_id, _1to3, [chains]) }
    for c in best_m.chains:
        if restrict is not None and c not in restrict_chains:
            continue
        chars = c.characters
        if chars in seq_entities:
            eid, _1to3, chains = seq_entities[chars]
            chains.append(c)
        else:
            mcid = c.existing_residues[0].mmcif_chain_id
            try:
                descrip = old_entity_to_description[old_mmcif_chain_to_entity[mcid]]
            except KeyError:
                descrip = '?'
            eid = len(entity_info) + 1
            entity_info[eid] = ('polymer', descrip)
            names = set(c.existing_residues.names)
            nstd = 'yes' if names.difference(_standard_residues) else 'no'
            # _1to3 is reverse map to handle missing residues
            if not best_guess and not c.from_seqres:
                skipped_sequence_info = True
                _1to3 = None
            else:
                if c.polymer_type == Residue.PT_AMINO:
                    _1to3 = _protein1to3
                    poly_info.append((eid, nstd, 'polypeptide(L)', chars))  # TODO: or polypeptide(D)
                elif names.isdisjoint(set(_rna1to3)):
                    # must be DNA
                    _1to3 = _dna1to3
                    poly_info.append((eid, nstd, 'polyribonucleotide', chars))
                else:
                    # must be RNA
                    _1to3 = _rna1to3
                    poly_info.append((eid, nstd, 'polydeoxyribonucleotide', chars))
            seq_entities[chars] = (eid, _1to3, [c])

    if skipped_sequence_info:
        session.logger.warning("Not saving entity_poly_seq for non-authoritative sequences")

    # use all chains of the same entity to figure out what the sequence's residues are named
    pdbx_poly_tmp = {}
    for chars, (eid, _1to3, chains) in seq_entities.items():
        chains = [c for c in chains if c.from_seqres]
        pdbx_poly_tmp[eid] = []
        for seq_id, ch, residues in zip(range(1, sys.maxsize), chars, zip(*(c.residues for c in chains))):
            label_seq_id = str(seq_id)
            for r in residues:
                if r is not None:
                    name = r.name
                    seq_num = r.number
                    ins_code = r.insertion_code
                    if not ins_code:
                        ins_code = '.'
                    break
            else:
                name = _1to3.get(ch, 'UNK')
                seq_num = '?'
                ins_code = '.'
            poly_seq_info.append((eid, label_seq_id, name))
            pdbx_poly_tmp[eid].append((name, label_seq_id, seq_num, ins_code))

    existing_mmcif_chain_ids = set(best_m.residues.mmcif_chain_ids)
    used_mmcif_chain_ids = set()
    last_asym_id = 0

    def get_asym_id(want_id):
        nonlocal existing_mmcif_chain_ids, used_mmcif_chain_ids, last_asym_id
        if want_id not in used_mmcif_chain_ids:
            used_mmcif_chain_ids.add(want_id)
            return want_id
        while True:
            last_asym_id += 1
            asym_id = _mmcif_chain_id(last_asym_id)
            if asym_id in existing_mmcif_chain_ids:
                continue
            used_mmcif_chain_ids.add(asym_id)
            return asym_id

    # assign label_asym_id's to each chain
    for c in best_m.chains:
        if restrict is not None and c not in restrict_chains:
            continue
        mcid = c.existing_residues[0].mmcif_chain_id
        label_asym_id = get_asym_id(mcid)
        chars = c.characters
        chain_id = c.chain_id
        eid, _1to3, _ = seq_entities[chars]
        asym_info[(chain_id, chars)] = (label_asym_id, eid)

        tmp = pdbx_poly_tmp[eid]
        for name, label_seq_id, seq_num, ins_code in tmp:
            pdbx_poly_info.append((eid, label_asym_id, name, label_seq_id, chain_id, seq_num, ins_code))
    del pdbx_poly_tmp

    het_entities = {}   # { het_name: { 'entity': entity_id, chain: (label_entity_id, label_asym_id) } }
    het_residues = concatenate(
        [m.residues.filter(m.residues.polymer_types == Residue.PT_NONE) for m in models])
    for r in het_residues:
        if restrict is not None and r not in restrict_residues:
            continue
        mcid = r.mmcif_chain_id
        n = r.name
        if n in het_entities:
            eid = het_entities[n]['entity']
        else:
            if n == 'HOH':
                etype = 'water'
            else:
                etype = 'non-polymer'
            try:
                descrip = old_entity_to_description[old_mmcif_chain_to_entity[mcid]]
            except KeyError:
                descrip = '?'
            eid = len(entity_info) + 1
            entity_info[eid] = (etype, descrip)
            het_entities[n] = {'entity': eid}
        if mcid in het_entities[n]:
            continue
        label_asym_id = get_asym_id(mcid)
        het_asym_info[mcid] = (label_asym_id, eid)
        het_entities[n][mcid] = (eid, label_asym_id)

    entity = mmcif.CIFTable('entity', ['id', 'type', 'pdbx_description'], flattened(entity_info.items()))
    entity.print(file, fixed_width=fixed_width)
    entity_poly = mmcif.CIFTable('entity_poly', ['entity_id', 'nstd_monomer', 'type', 'pdbx_seq_one_letter_code_can'], flattened(poly_info))
    entity_poly.print(file, fixed_width=fixed_width)
    entity_poly_seq = mmcif.CIFTable('entity_poly_seq', ['entity_id', 'num', 'mon_id'], flattened(poly_seq_info))
    entity_poly_seq.print(file, fixed_width=fixed_width)
    import itertools
    struct_asym = mmcif.CIFTable(
        'struct_asym', ['id', 'entity_id'],
        flattened(itertools.chain(asym_info.values(), het_asym_info.values())))
    struct_asym.print(file, fixed_width=fixed_width)
    pdbx_poly_seq = mmcif.CIFTable('pdbx_poly_seq_scheme', ['entity_id', 'asym_id', 'mon_id', 'seq_id', 'pdb_strand_id', 'pdb_seq_num', 'pdb_ins_code'], flattened(pdbx_poly_info))
    pdbx_poly_seq.print(file, fixed_width=fixed_width)
    del entity, entity_poly_seq, pdbx_poly_seq, struct_asym

    elements = list(set(best_m.atoms.elements))
    elements.sort(key=lambda e: e.number)
    atom_type_data = [e.name for e in elements]
    atom_type = mmcif.CIFTable("atom_type", ["symbol"], atom_type_data)
    atom_type.print(file, fixed_width=fixed_width)
    del atom_type_data, atom_type

    atom_site_data = []
    atom_site = mmcif.CIFTable("atom_site", [
        'group_PDB', 'id', 'type_symbol', 'label_atom_id', 'label_alt_id',
        'label_comp_id', 'label_asym_id', 'label_entity_id', 'label_seq_id',
        'Cartn_x', 'Cartn_y', 'Cartn_z',
        'auth_asym_id', 'auth_seq_id', 'pdbx_PDB_ins_code',
        'occupancy', 'B_iso_or_equiv', 'pdbx_PDB_model_num'
    ], atom_site_data)
    atom_site_anisotrop_data = []
    atom_site_anisotrop = mmcif.CIFTable("atom_site_anisotrop", [
        'id', 'type_symbol',
        'U[1][1]', 'U[2][2]', 'U[3][3]',
        'U[1][2]', 'U[1][3]', 'U[2][3]',
    ], atom_site_anisotrop_data)
    serial_num = 0

    def atom_site_residue(residue, seq_id, asym_id, entity_id, model_num, xform):
        nonlocal serial_num, residue_info, atom_site_data, atom_site_anisotrop_data
        residue_info[residue] = (asym_id, seq_id)
        atoms = residue.atoms
        rname = residue.name
        cid = residue.chain_id
        if cid == ' ':
            cid = '.'
        rnum = residue.number
        rins = residue.insertion_code
        if not rins:
            rins = '?'
        if rname in _standard_residues:
            group = 'ATOM'
        else:
            group = 'HETATM'
        for atom in atoms:
            if restrict is not None:
                if atom not in restrict:
                    continue
            elem = atom.element.name
            aname = atom.name
            original_alt_loc = atom.alt_loc
            for alt_loc in atom.alt_locs or '.':
                if alt_loc != '.':
                    atom.set_alt_loc(alt_loc, False)
                coord = atom.coord
                if xform is not None:
                    coord = xform * coord
                xyz = ['%.3f' % f for f in coord]
                occ = "%.2f" % atom.occupancy
                bfact = "%.2f" % atom.bfactor
                serial_num += 1
                atom_site_data.append((
                    group, serial_num, elem, aname, alt_loc, rname, asym_id,
                    entity_id, seq_id, *xyz, cid, rnum, rins, occ, bfact,
                    model_num))
                u6 = atom.aniso_u6
                if u6 is not None and len(u6) > 0:
                    u6 = ['%.4f' % f for f in u6]
                    atom_site_anisotrop_data.append((serial_num, elem, u6))
            if alt_loc != '.':
                atom.set_alt_loc(original_alt_loc, False)

    for m, xform, model_num in zip(models, xforms, range(1, sys.maxsize)):
        residues = m.residues
        het_residues = residues.filter(residues.polymer_types == Residue.PT_NONE)
        for c in m.chains:
            if restrict is not None and c not in restrict_chains:
                continue
            chain_id = c.chain_id
            chars = c.characters
            asym_id, entity_id = asym_info[(chain_id, chars)]
            for seq_id, r in zip(range(1, sys.maxsize), c.residues):
                if r is None:
                    continue
                if restrict is not None and r not in restrict_residues:
                    continue
                atom_site_residue(r, seq_id, asym_id, entity_id, model_num, xform)
            chain_het = het_residues.filter(het_residues.chain_ids == chain_id)
            het_residues -= chain_het
            for r in chain_het:
                if restrict is not None and r not in restrict_residues:
                    continue
                asym_id, entity_id = het_asym_info[r.mmcif_chain_id]
                atom_site_residue(r, '.', asym_id, entity_id, model_num, xform)
        for r in het_residues:
            if restrict is not None and r not in restrict_residues:
                continue
            asym_id, entity_id = het_asym_info[r.mmcif_chain_id]
            atom_site_residue(r, '.', asym_id, entity_id, model_num, xform)

    atom_site_data[:] = flattened(atom_site_data)
    atom_site.print(file, fixed_width=fixed_width)
    atom_site_anisotrop_data[:] = flattened(atom_site_anisotrop_data)
    atom_site_anisotrop.print(file, fixed_width=fixed_width)
    del atom_site_data, atom_site, atom_site_anisotrop_data, atom_site_anisotrop

    struct_conn_data = []
    struct_conn = mmcif.CIFTable("struct_conn", [
        "id", "conn_type_id",
        "ptnr1_label_atom_id",
        "pdbx_ptnr1_label_alt_id",
        "ptnr1_label_asym_id",
        "ptnr1_label_seq_id",
        "ptnr1_auth_asym_id",
        "ptnr1_auth_seq_id",
        "pdbx_ptnr1_PDB_ins_code",
        "ptnr1_label_comp_id",
        "ptnr1_symmetry",
        "ptnr2_label_atom_id",
        "pdbx_ptnr2_label_alt_id",
        "ptnr2_label_asym_id",
        "ptnr2_label_seq_id",
        "ptnr2_auth_asym_id",
        "ptnr2_auth_seq_id",
        "pdbx_ptnr2_PDB_ins_code",
        "ptnr2_label_comp_id",
        "ptnr2_symmetry",
        "pdbx_dist_value",
    ], struct_conn_data)

    struct_conn_type_data = []
    struct_conn_type = mmcif.CIFTable("struct_conn_type", [
        "id",
    ], struct_conn_type_data)

    def struct_conn_bond(tag, b, a0, a1):
        nonlocal count, struct_conn_data
        r0 = a0.residue
        r1 = a1.residue
        r0_asym, r0_seq = residue_info[r0]
        r1_asym, r1_seq = residue_info[r1]
        cid0 = r0.chain_id
        if cid0 == ' ':
            cid0 = '.'
        rnum0 = r0.number
        rins0 = r0.insertion_code
        if not rins0:
            rins0 = '?'
        cid1 = r1.chain_id
        if cid1 == ' ':
            cid1 = '.'
        rnum1 = r1.number
        rins1 = r1.insertion_code
        if not rins1:
            rins1 = '?'
        # find all alt_loc pairings
        alt_pairs = []
        for alt_loc in a0.alt_locs:
            if a1.has_alt_loc(alt_loc):
                alt_pairs.append((alt_loc, alt_loc))
            elif not a1.alt_locs:
                alt_pairs.append((alt_loc, ' '))
        else:
            for alt_loc in a1.alt_locs:
                alt_pairs.append((' ', alt_loc))
            else:
                alt_pairs.append((' ', ' '))
        original_alt_loc0 = a0.alt_loc
        original_alt_loc1 = a1.alt_loc
        for alt_loc0, alt_loc1 in alt_pairs:
            if alt_loc0 == ' ':
                alt_loc0 = '.'
            else:
                a0.set_alt_loc(alt_loc0, False)
            if alt_loc1 == ' ':
                alt_loc1 = '.'
            else:
                a1.set_alt_loc(alt_loc1, False)
            dist = "%.3f" % b.length
            count += 1
            struct_conn_data.append((
                '%s%d' % (tag, count), tag,
                a0.name, alt_loc0, r0_asym, r0_seq, cid0, rnum0, rins0, r0.name, "1_555",
                a1.name, alt_loc1, r1_asym, r1_seq, cid1, rnum1, rins1, r1.name, "1_555",
                dist))
        if original_alt_loc0 != ' ':
            a0.set_alt_loc(original_alt_loc0, False)
        if original_alt_loc1 != ' ':
            a1.set_alt_loc(original_alt_loc1, False)

    # disulfide bonds
    count = 0
    atoms = best_m.atoms
    if restrict is not None:
        atoms = atoms.intersect(restrict)
    bonds = best_m.bonds
    has_disulf = False
    covalent = []
    sg = atoms.filter(atoms.names == "SG")
    for b, a0, a1 in zip(bonds, *bonds.atoms):
        if a0 in sg and a1 in sg:
            has_disulf = True
            struct_conn_bond('disulf', b, a0, a1)
            continue
        r0 = a0.residue
        r1 = a1.residue
        if restrict is not None:
            if r0 not in restrict_residues or r1 not in restrict_residues:
                continue
        if r0 == r1:
            continue
        if r0.chain is None or r0.chain != r1.chain:
            covalent.append((b, a0, a1))
        elif r0.name not in _standard_residues or r1.name not in _standard_residues:
            covalent.append((b, a0, a1))
        else:
            # check for non-implicit bond
            res_map = r0.chain.res_map
            r0index = res_map[r0]
            r1index = res_map[r1]
            if abs(r1index - r0index) != 1:
                # not adjacent (circular)
                covalent.append((b, a0, a1))
            elif b.polymeric_start_atom is None:
                # non-polymeric bond
                covalent.append((b, a0, a1))
    if has_disulf:
        struct_conn_type_data.append('disulf')

    # metal coordination bonds
    # assume intra-residue metal coordination bonds are handled by residue template
    count = 0
    pbg = best_m.pseudobond_group(best_m.PBG_METAL_COORDINATION, create_type=None)
    if pbg:
        bonds = pbg.pseudobonds
        if len(bonds) > 0:
            struct_conn_type_data.append('metalc')
        for b, a0, a1 in zip(bonds, *bonds.atoms):
            r0 = a0.residue
            r1 = a1.residue
            if restrict is not None:
                if r0 not in restrict_residues or r1 not in restrict_residues:
                    continue
            if r0 == r1:
                continue
            struct_conn_bond('metalc', b, a0, a1)

    # hydrogen bonds
    count = 0
    pbg = best_m.pseudobond_group(best_m.PBG_HYDROGEN_BONDS, create_type=None)
    if pbg:
        bonds = pbg.pseudobonds
        if len(bonds) > 0:
            struct_conn_type_data.append('hydrog')
        for b, a0, a1 in zip(bonds, *bonds.atoms):
            if restrict is not None:
                if a0 not in restrict or a1 not in restrict:
                    continue
            struct_conn_bond('hydrog', b, a0, a1)

    # extra/other covalent bonds
    # TODO: covalent bonds not in resdiue template
    count = 0
    if len(covalent) > 0:
        struct_conn_type_data.append('covale')
    for b, a0, a1 in covalent:
        if restrict is not None:
            if a0 not in restrict or a1 not in restrict:
                continue
        struct_conn_bond('covale', b, a0, a1)

    struct_conn_data[:] = flattened(struct_conn_data)
    struct_conn.print(file, fixed_width=fixed_width)
    # struct_conn_type_data[:] = flattened(struct_conn_type_data)
    struct_conn_type.print(file, fixed_width=fixed_width)
    del struct_conn_data, struct_conn, struct_conn_type_data, struct_conn_type

    # struct_conf
    struct_conf_data = []
    struct_conf = mmcif.CIFTable("struct_conf", [
        "id", "conf_type_id",
        "beg_label_comp_id",
        "beg_label_asym_id",
        "beg_label_seq_id",
        "end_label_comp_id",
        "end_label_asym_id",
        "end_label_seq_id",
        "beg_auth_asym_id",
        "beg_auth_seq_id",
        "pdbx_beg_PDB_ins_code",
        "end_auth_asym_id",
        "end_auth_seq_id",
        "pdbx_end_PDB_ins_code",
    ], struct_conf_data)

    struct_conf_type_data = []
    struct_conf_type = mmcif.CIFTable("struct_conf_type", [
        "id"
    ], struct_conf_type_data)

    def struct_conf_entry(id, ctype, beg_res, end_res):
        nonlocal struct_conf_data
        beg_asym, beg_seq = residue_info[beg_res]
        end_asym, end_seq = residue_info[end_res]
        beg_cid = beg_res.chain_id
        if beg_cid == ' ':
            beg_cid = '.'
        beg_rnum = beg_res.number
        beg_rins = beg_res.insertion_code
        if not beg_rins:
            beg_rins = '?'
        end_cid = end_res.chain_id
        if end_cid == ' ':
            end_cid = '.'
        end_rnum = end_res.number
        end_rins = end_res.insertion_code
        if not end_rins:
            end_rins = '?'
        struct_conf_data.append((
            id, ctype,
            beg_res.name, beg_asym, beg_seq,
            end_res.name, end_asym, end_seq,
            beg_cid, beg_rnum, beg_rins,
            end_cid, end_rnum, end_rins))

    sheet_range_data = []
    sheet_range = mmcif.CIFTable("struct_sheet_range", [
        "sheet_id", "id",
        "beg_label_comp_id",
        "beg_label_asym_id",
        "beg_label_seq_id",
        "end_label_comp_id",
        "end_label_asym_id",
        "end_label_seq_id",
        "symmetry",
        "beg_auth_asym_id",
        "beg_auth_seq_id",
        "pdbx_beg_PDB_ins_code",
        "end_auth_asym_id",
        "end_auth_seq_id",
        "pdbx_end_PDB_ins_code",
    ], sheet_range_data)

    def sheet_range_entry(sheet_id, count, beg_res, end_res, symmetry="1_555"):
        nonlocal sheet_range_data
        beg_asym, beg_seq = residue_info[beg_res]
        end_asym, end_seq = residue_info[end_res]
        beg_cid = beg_res.chain_id
        if beg_cid == ' ':
            beg_cid = '.'
        beg_rnum = beg_res.number
        beg_rins = beg_res.insertion_code
        if not beg_rins:
            beg_rins = '?'
        end_cid = end_res.chain_id
        if end_cid == ' ':
            end_cid = '.'
        end_rnum = end_res.number
        end_rins = end_res.insertion_code
        if not end_rins:
            end_rins = '?'
        sheet_range_data.append((
            sheet_id, count,
            beg_res.name, beg_asym, beg_seq,
            end_res.name, end_asym, end_seq,
            symmetry,
            beg_cid, beg_rnum, beg_rins,
            end_cid, end_rnum, end_rins))

    helix_count = 0
    strand_count = 0
    residues = best_m.residues
    ssids = residues.secondary_structure_ids
    last_ssid = 0
    beg_res = None
    end_res = None
    for r, ssid in zip(residues, ssids):
        if last_ssid == 0:
            beg_res = end_res = r
            last_ssid = ssid
        elif ssid == last_ssid:
            end_res = r
        else:
            skip = False
            if restrict is not None:
                skip = beg_res not in restrict_residues or end_res not in restrict_residues
            if skip:
                pass
            elif beg_res.is_helix:
                helix_count += 1
                struct_conf_entry('HELX%d' % helix_count, "HELX_P", beg_res, end_res)
            elif beg_res.is_strand:
                strand_count += 1
                sheet_range_entry('?', strand_count, beg_res, end_res)
            beg_res = end_res = r
            last_ssid = ssid
    if last_ssid:
        skip = False
        if restrict is not None:
            skip = beg_res not in restrict_residues or end_res not in restrict_residues
        if skip:
            pass
        elif beg_res.is_helix:
            helix_count += 1
            struct_conf_entry('HELX%d' % helix_count, "HELX_P", beg_res, end_res)
        elif beg_res.is_strand:
            strand_count += 1
            struct_conf_entry('STRN%d' % strand_count, "STRN_P", beg_res, end_res)

    if helix_count:
        struct_conf_type_data.append("HELX_P")

    struct_conf_data[:] = flattened(struct_conf_data)
    struct_conf.print(file, fixed_width=fixed_width)
    # struct_conf_type_data[:] = flattened(struct_conf_type_data)
    struct_conf_type.print(file, fixed_width=fixed_width)
    del struct_conf_data, struct_conf, struct_conf_type_data, struct_conf_type
    sheet_range_data[:] = flattened(sheet_range_data)
    sheet_range.print(file, fixed_width=fixed_width)
    del sheet_range_data, sheet_range

    _save_metadata(best_m, ['entity_src_gen', 'entity_src_nat'], file, best_metadata)
    _save_metadata(best_m, ['cell', 'symmetry'], file, best_metadata)
    _save_metadata(best_m, ['pdbx_struct_assembly', 'pdbx_struct_assembly_gen', 'pdbx_struct_oper_list'], file, best_metadata)
Example #2
0
def _cmd(session,
         test_atoms,
         name,
         hbond_allowance,
         overlap_cutoff,
         test_type,
         color,
         radius,
         *,
         attr_name=defaults["attr_name"],
         bond_separation=defaults["bond_separation"],
         continuous=False,
         dashes=None,
         distance_only=None,
         inter_model=True,
         inter_submodel=False,
         intra_model=True,
         intra_mol=defaults["intra_mol"],
         intra_res=defaults["intra_res"],
         log=defaults["action_log"],
         make_pseudobonds=defaults["action_pseudobonds"],
         naming_style=None,
         res_separation=None,
         restrict="any",
         reveal=False,
         save_file=None,
         set_attrs=defaults["action_attr"],
         select=defaults["action_select"],
         show_dist=False,
         summary=True):
    from chimerax.core.errors import UserError
    if test_atoms is None:
        from chimerax.atomic import AtomicStructure, AtomicStructures
        test_atoms = AtomicStructures([
            s for s in session.models if isinstance(s, AtomicStructure)
        ]).atoms
    if not test_atoms:
        raise UserError("No atoms match given atom specifier")
    from chimerax.core.colors import Color
    if color is not None and not isinstance(color, Color):
        color = Color(rgba=color)
    from chimerax.atomic import get_triggers
    ongoing = False
    if continuous:
        if set_attrs or save_file != None or log:
            raise UserError(
                "log/setAttrs/saveFile not allowed with continuous detection")
        if getattr(session, _continuous_attr, None) == None:
            from inspect import getargvalues, currentframe, getfullargspec
            arg_names, fArgs, fKw, frame_dict = getargvalues(currentframe())
            arg_spec = getfullargspec(_cmd)
            args = [frame_dict[an] for an in arg_names[:len(arg_spec.args)]]
            kw = {k: frame_dict[k] for k in arg_names[len(arg_spec.args):]}
            call_data = (args, kw)

            def changes_cb(trig_name,
                           changes,
                           session=session,
                           call_data=call_data):
                s_reasons = changes.atomic_structure_reasons()
                a_reasons = changes.atom_reasons()
                if 'position changed' in s_reasons \
                or 'active_coordset changed' in s_reasons \
                or 'coord changed' in a_reasons \
                or 'alt_loc changed' in a_reasons:
                    args, kw = call_data
                    if not args[1]:
                        # all atoms gone
                        delattr(session, _continuous_attr)
                        from chimerax.core.triggerset import DEREGISTER
                        return DEREGISTER
                    _cmd(*tuple(args), **kw)

            setattr(session, _continuous_attr,
                    get_triggers().add_handler('changes', changes_cb))
        else:
            ongoing = True
    elif getattr(session, _continuous_attr, None) != None:
        get_triggers().remove_handler(getattr(session, _continuous_attr))
        delattr(session, _continuous_attr)
    from .clashes import find_clashes
    clashes = find_clashes(session,
                           test_atoms,
                           attr_name=attr_name,
                           bond_separation=bond_separation,
                           clash_threshold=overlap_cutoff,
                           distance_only=distance_only,
                           hbond_allowance=hbond_allowance,
                           inter_model=inter_model,
                           inter_submodel=inter_submodel,
                           intra_model=intra_model,
                           intra_res=intra_res,
                           intra_mol=intra_mol,
                           res_separation=res_separation,
                           restrict=restrict)
    if select:
        session.selection.clear()
        for a in clashes.keys():
            a.selected = True
    # if relevant, put the test_atoms in the first column
    if restrict == "both":
        output_grouping = set()
    else:
        output_grouping = test_atoms
    test_type = "distances" if distance_only else test_type
    info = (overlap_cutoff, hbond_allowance, bond_separation, intra_res,
            intra_mol, clashes, output_grouping, test_type, res_separation)
    if log:
        import io
        buffer = io.StringIO()
        buffer.write("<pre>")
        _file_output(buffer, info, naming_style)
        buffer.write("</pre>")
        session.logger.info(buffer.getvalue(), is_html=True)
    if save_file is not None:
        _file_output(save_file, info, naming_style)
    if summary:
        if clashes:
            total = 0
            for clash_list in clashes.values():
                total += len(clash_list)
            session.logger.status("%d %s" % (total / 2, test_type),
                                  log=not ongoing)
        else:
            session.logger.status("No %s" % test_type, log=not ongoing)
    if not (set_attrs or make_pseudobonds or reveal):
        _xcmd(session, name)
        return clashes
    from chimerax.atomic import all_atoms
    if restrict == "both":
        attr_atoms = test_atoms
    elif restrict in ("any", "cross"):
        if inter_model:
            attr_atoms = all_atoms(session)
        else:
            attr_atoms = test_atoms.unique_structures.atoms
    else:
        from chimerax.atomic import concatenate
        attr_atoms = concatenate([test_atoms, restrict],
                                 remove_duplicates=True)
    from chimerax.atomic import Atoms
    clash_atoms = Atoms([a for a in attr_atoms if a in clashes])
    if set_attrs:
        # delete the attribute in _all_ atoms...
        for a in all_atoms(session):
            if hasattr(a, attr_name):
                delattr(a, attr_name)
        for a in clash_atoms:
            clash_vals = list(clashes[a].values())
            clash_vals.sort()
            setattr(a, attr_name, clash_vals[-1])
    if reveal:
        # display sidechain or backbone as appropriate for undisplayed atoms
        reveal_atoms = clash_atoms.filter(clash_atoms.displays == False)
        reveal_residues = reveal_atoms.unique_residues
        sc_rv_atoms = reveal_atoms.filter(reveal_atoms.is_side_chains == True)
        if sc_rv_atoms:
            sc_residues = sc_rv_atoms.unique_residues
            sc_res_atoms = sc_residues.atoms
            sc_res_atoms.filter(
                sc_res_atoms.is_side_chains == True).displays = True
            reveal_residues = reveal_residues - sc_residues
        bb_rv_atoms = reveal_atoms.filter(reveal_atoms.is_backbones() == True)
        if bb_rv_atoms:
            bb_residues = bb_rv_atoms.unique_residues
            bb_res_atoms = bb_residues.atoms
            bb_res_atoms.filter(
                bb_res_atoms.is_backbones() == True).displays = True
            reveal_residues = reveal_residues - bb_residues
        # also reveal non-polymeric atoms
        reveal_residues.atoms.displays = True
    if make_pseudobonds:
        if len(attr_atoms.unique_structures) > 1:
            pbg = session.pb_manager.get_group(name)
        else:
            pbg = attr_atoms[0].structure.pseudobond_group(name)
        pbg.clear()
        pbg.radius = radius
        if color is not None:
            pbg.color = color.uint8x4()
        if dashes is not None:
            pbg.dashes = dashes
        seen = set()
        for a in clash_atoms:
            seen.add(a)
            for clasher in clashes[a].keys():
                if clasher in seen:
                    continue
                pbg.new_pseudobond(a, clasher)
        if show_dist:
            session.pb_dist_monitor.add_group(pbg)
        else:
            session.pb_dist_monitor.remove_group(pbg)
        if pbg.id is None:
            session.models.add([pbg])
    else:
        _xcmd(session, name)
    return clashes