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)
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