def save(self, output, format_name="fasta"): import importlib mod = importlib.import_module(".io.save%s" % format_name.upper(), "chimerax.seqalign") from chimerax import io with io.open_output(output, 'utf-8') as stream: mod.save(self.session, self, stream)
def distance_save(session, save_file_name): from chimerax.io import open_output save_file = open_output(save_file_name, 'utf-8') from chimerax.atomic import Structure for model in session.models: if not isinstance(model, Structure): continue print("Model", model.id_string, "is", model.name, file=save_file) print("\nDistance information:", file=save_file) grp = session.pb_manager.get_group("distances", create=False) if grp: pbs = list(grp.pseudobonds) pbs.sort(key=lambda pb: pb.length) fmt = "%s <-> %s: " + session.pb_dist_monitor.distance_format for pb in pbs: a1, a2 = pb.atoms d_string = fmt % (a1, a2.string(relative_to=a1), pb.length) # drop angstrom symbol... if not d_string[-1].isdigit(): d_string = d_string[:-1] print(d_string, file=save_file) if save_file_name != save_file: # Wasn't a stream that was passed in... save_file.close()
def _save(self, session, file_name): from chimerax.io import open_output with open_output(file_name, encoding="utf-8") as f: if hasattr(self, 'save_file_preamble'): print(self.save_file_preamble, file=f) print("%s header for %s" % (self.name, self.alignment), file=f) for i, val in enumerate(self): print("%d:" % (i+1), val, file=f)
def _file_output(file_name, info, naming_style): overlap_cutoff, hbond_allowance, bond_separation, intra_res, intra_mol, \ clashes, output_grouping, test_type, res_separation = info from chimerax.io import open_output out_file = open_output(file_name, 'utf-8') if test_type != "distances": print("Allowed overlap: %g" % overlap_cutoff, file=out_file) print("H-bond overlap reduction: %g" % hbond_allowance, file=out_file) print("Ignore %s between atoms separated by %d bonds or less" % (test_type, bond_separation), file=out_file) if res_separation: print( "Ignore %s between atoms in residues less than %d apart in sequence" % (test_type, res_separation), file=out_file) print("Detect intra-residue %s:" % test_type, intra_res, file=out_file) print("Detect intra-molecule %s:" % test_type, intra_mol, file=out_file) seen = set() data = [] from chimerax.geometry import distance for a, aclashes in clashes.items(): for c, val in aclashes.items(): if (c, a) in seen: continue seen.add((a, c)) if a in output_grouping: out1, out2 = a, c else: out1, out2 = c, a l1, l2 = out1.string(style=naming_style), out2.string( style=naming_style) data.append( (val, l1, l2, distance(out1.scene_coord, out2.scene_coord))) data.sort() data.reverse() print("\n%d %s" % (len(data), test_type), file=out_file) field_width1 = max([len(l1) for v, l1, l2, d in data] + [5]) field_width2 = max([len(l2) for v, l1, l2, d in data] + [5]) #print("%*s %*s overlap distance" % (0-field_width1, "atom1", 0-field_width2, "atom2"), print( f"{'atom1':^{field_width1}} {'atom2':^{field_width2}} overlap distance", file=out_file) for v, l1, l2, d in data: print(f"%*s %*s %5.3f %5.3f" % (0 - field_width1, l1, 0 - field_width2, l2, v, d), file=out_file) if file_name != out_file: # only close file if we opened it... out_file.close()
def save_xyz(session, path, structures=None): """Write an XYZ file from given models, or all models if None. """ # Open path with proper encoding; 'open_output' automatically # handles compression if the file name also has a compression # suffix (e.g. .gz) from chimerax.io import open_output f = open_output(path, session.data_formats['XYZ'].encoding) # If no models were given, use all atomic structures if structures is None: from chimerax.atomic import AtomicStructure structures = session.models.list(type=AtomicStructure) num_atoms = 0 # Loop through structures and print atoms for s in structures: # We get the list of atoms and transformed atomic coordinates # as arrays so that we can limit the number of accesses to # molecular data, which is slower than accessing arrays directly atoms = s.atoms coords = atoms.scene_coords # First line for a structure is the number of atoms print(str(s.num_atoms), file=f) # Second line is a comment print(getattr(s, "name", "unnamed"), file=f) # One line per atom thereafter for i in range(len(atoms)): a = atoms[i] c = coords[i] print("%s %.3f %.3f %.3f" % (a.element, c[0], c[1], c[2]), file=f) num_atoms += s.num_atoms f.close() # Notify user that file was saved session.logger.status( "Saved XYZ file containing %d structures (%d atoms)" % (len(structures), num_atoms))
def write_mol2(session, file_name, *, models=None, atoms=None, status=None, anchor=None, rel_model=None, sybyl_hyd_naming=True, combine_models=False, skip_atoms=None, res_num=False, gaff_type=False, gaff_fail_error=None): """Write a Mol2 file. Parameters ---------- file_name : str, or file object open for writing Output file. models : a list/tuple/set of models (:py:class:`~chimerax.atomic.Structure`s) or a single :py:class:`~chimerax.atomic.Structure` The structure(s) to write out. If None (and 'atoms' is also None) then write out all structures. atoms : an :py:class:`~chimerax.atomic.Atoms` collection or None. If not None, then 'models' must be None. status : function or None If not None, a function that takes a string -- used to report the progress of the write. anchor : :py:class:`~chimerax.atomic.Atoms` collection Atoms (and their implied internal bonds) that should be written out to the @SET section of the file as the rigid framework for flexible ligand docking. rel_model : Model whose coordinate system the coordinates should be written out reletive to, i.e. take the output atoms' coordinates and apply the inverse of the rel_model's transform. sybyl_hyd_naming : bool Controls whether hydrogen names should be "Sybyl-like" or "PDB-like" -- e.g. HG21 vs. 1HG2. combine_models : bool Controls whether multiple structures will be combined into a single @MOLECULE section (value: True) or each given its own section (value: False). skip_atoms : list/set of :py:class:`~chimerax.atomic.Atom`s or an :py:class:`~chimerax.atomic.Atoms` collection or None Atoms to not output res_num : bool Controls whether residue sequence numbers are included in the substructure name. Since Sybyl Mol2 files include them, this defaults to True. gaff_type : bool If 'gaff_type' is True, outout GAFF atom types instead of Sybyl atom types. `gaff_fail_error`, if specified, is the type of error to throw (e.g. UserError) if there is no gaff_type attribute for an atom, otherwise throw the standard AttributeError. """ if status: status("Writing Mol2 file %s" % file_name) from chimerax import io f = io.open_output(file_name, "utf-8") sort_key_func = serial_sort_key = lambda a, ri={}: write_mol2_sort_key( a, res_indices=ri) from chimerax.atomic import Structure, Atoms, Residue class JPBGroup: def __init__(self, atoms): atom_set = set(atoms) pbs = [] for s in atoms.unique_structures: pbg = s.pbg_map.get(s.PBG_METAL_COORDINATION, None) if not pbg: continue for pb in pbg.pseudobonds: if pb.atoms[0] in atom_set and pb.atoms[1] in atom_set: pbs.append(pb) self._pbs = pbs @property def pseudobonds(self): return self._pbs if models is None: if atoms is None: structures = session.models.list(type=Structure) else: structures = atoms else: if atoms is None: if isinstance(models, Structure): structures = [models] else: structures = [m for m in models if isinstance(m, Structure)] else: raise ValueError( "Cannot specify both 'models' and 'atoms' keywords") if isinstance(structures, Atoms): class Jumbo: def __init__(self, atoms): self.atoms = atoms self.residues = atoms.unique_residues self.bonds = atoms.intra_bonds self.name = "(selection)" self.pbg_map = { Structure.PBG_METAL_COORDINATION: JPBGroup(atoms) } structures = [Jumbo(structures)] sort_key_func = lambda a: (a.structure.id, ) + serial_sort_key(a) combine_models = False # transform... if rel_model is None: from chimerax.geometry import identity xform = identity() else: xform = rel_model.scene_position.inverse() # need to find amide moieties since Sybyl has an explicit amide type if status: status("Finding amides") from chimerax.chem_group import find_group amides = find_group("amide", structures) amide_Ns = set([amide[2] for amide in amides]) amide_CNs = set([amide[0] for amide in amides]) amide_CNs.update(amide_Ns) amide_Os = set([amide[1] for amide in amides]) substructure_names = None if combine_models and len(structures) > 1: # create a fictitious jumbo model class Jumbo: def __init__(self, structures): self.name = structures[0].name + " (combined)" from chimerax.atomic import concatenate self.atoms = concatenate([s.atoms for s in structures]) self.bonds = concatenate([s.bonds for s in structures]) self.residues = concatenate([s.residues for s in structures]) self.pbg_map = { Structure.PBG_METAL_COORDINATION: JPBGroup(self.atoms) } # if combining single-residue structures, # can be more informative to use model name # instead of residue type for substructure if len(structures) == len(self.residues): rnames = self.residues.names if len(set(rnames)) < len(rnames): snames = [s.name for s in structures] if len(set(snames)) == len(snames): self.substructure_names = dict( zip(self.residues, snames)) structures = [Jumbo(structures)] if hasattr(structures[-1], 'substructure_names'): substructure_names = structures[-1].substructure_names delattr(structures[-1], 'substructure_names') sort_key_func = lambda a: (a.structure.id, ) + serial_sort(a) # write out structures for struct in structures: if hasattr(struct, 'mol2_comments'): for m2c in struct.mol2_comments: print(m2c, file=f) if hasattr(struct, 'solvent_info'): print(struct.solvent_info, file=f) # molecule section header print("%s" % MOLECULE_HEADER, file=f) # molecule name print("%s" % struct.name, file=f) atoms = list(struct.atoms) bonds = list(struct.bonds) # add metal-coordination bonds coord_grp = struct.pbg_map.get(Structure.PBG_METAL_COORDINATION, None) if coord_grp: bonds.extend(list(coord_grp.pseudobonds)) if skip_atoms: skip_atoms = set(skip_atoms) atoms = [a for a in atoms if a not in skip_atoms] bonds = [ b for b in bonds if b.atoms[0] not in skip_atoms and b.atoms[1] not in skip_atoms ] residues = struct.residues # Put the atoms in the order we want for output if status: status("Putting atoms in input order") atoms.sort(key=sort_key_func) # if anchor is not None, then there will be two entries in # the @SET section of the file... if anchor: sets = 2 else: sets = 0 # number of entries for various sections... print("%d %d %d 0 %d" % (len(atoms), len(bonds), len(residues), sets), file=f) # type of molecule if hasattr(struct, "mol2_type"): mtype = struct.mol2_type else: mtype = "SMALL" from chimerax.atomic import Sequence for r in struct.residues: if Sequence.protein3to1(r.name) != 'X': mtype = "PROTEIN" break if Sequence.nucleic3to1(r.name) != 'X': mtype = "NUCLEIC_ACID" break print(mtype, file=f) # indicate type of charge information if hasattr(struct, 'charge_model'): print(struct.charge_model, file=f) else: print("NO_CHARGES", file=f) if hasattr(struct, 'mol2_comment'): print("\n%s" % struct.mol2_comment, file=f) else: print("\n", file=f) if status: status("writing atoms") # atom section header print("%s" % ATOM_HEADER, file=f) # make a dictionary of residue indices so that we can do quick look ups res_indices = {} for i, r in enumerate(residues): res_indices[r] = i + 1 for i, atom in enumerate(atoms): # atom ID, starting from 1 print("%7d" % (i + 1), end=" ", file=f) # atom name, possibly rearranged if it's a hydrogen if sybyl_hyd_naming and not atom.name[0].isalpha(): atom_name = atom.name[1:] + atom.name[0] else: atom_name = atom.name print("%-8s" % atom_name, end=" ", file=f) # use correct relative coordinate position coord = xform * atom.scene_coord print("%9.4f %9.4f %9.4f" % tuple(coord), end=" ", file=f) # atom type if gaff_type: try: atom_type = atom.gaff_type except AttributeError: if not gaff_fail_error: raise raise gaff_fail_error( "%s has no Amber/GAFF type assigned.\n" "Use the AddCharge tool to assign Amber/GAFF types." % atom) elif hasattr(atom, 'mol2_type'): atom_type = atom.mol2_type elif atom in amide_Ns: atom_type = "N.am" elif atom.structure_category == "solvent" \ and atom.residue.name in Residue.water_res_names: if atom.element.name == "O": atom_type = "O.t3p" else: atom_type = "H.t3p" elif atom.element.name == "N" and len( [r for r in atom.rings() if r.aromatic]) > 0: atom_type = "N.ar" elif atom.idatm_type == "C2" and len( [nb for nb in atom.neighbors if nb.idatm_type == "Ng+"]) > 2: atom_type = "C.cat" elif sulfur_oxygen(atom): atom_type = "O.2" else: try: atom_type = chimera_to_sybyl[atom.idatm_type] except KeyError: session.logger.warning( "Atom whose IDATM type has no equivalent" " Sybyl type: %s (type: %s)" % (atom, atom.idatm_type)) atom_type = str(atom.element) print("%-5s" % atom_type, end=" ", file=f) # residue-related info res = atom.residue # residue index print("%5d" % res_indices[res], end=" ", file=f) # substructure identifier and charge if hasattr(atom, 'charge') and atom.charge is not None: charge = atom.charge else: charge = 0.0 if substructure_names: rname = substructure_names[res] elif res_num: rname = "%3s%-5d" % (res.name, res.number) else: rname = "%3s" % res.name print("%s %9.4f" % (rname, charge), file=f) if status: status("writing bonds") # bond section header print("%s" % BOND_HEADER, file=f) # make an atom-index dictionary to speed lookups atom_indices = {} for i, a in enumerate(atoms): atom_indices[a] = i + 1 for i, bond in enumerate(bonds): a1, a2 = bond.atoms # ID print("%6d" % (i + 1), end=" ", file=f) # atom IDs print("%4d %4d" % (atom_indices[a1], atom_indices[a2]), end=" ", file=f) # bond order; give it our best shot... if hasattr(bond, 'mol2_type'): print(bond.mol2_type, file=f) continue amide_A1 = a1 in amide_CNs amide_A2 = a2 in amide_CNs if amide_A1 and amide_A2: print("am", file=f) continue if amide_A1 or amide_A2: if a1 in amide_Os or a2 in amide_Os: print("2", file=f) else: print("1", file=f) continue aromatic = False # 'bond' might be a metal-coordination bond so do a test for rings if hasattr(bond, 'rings'): for ring in bond.rings(): if ring.aromatic: aromatic = True break if aromatic: print("ar", file=f) continue try: geom1 = idatm_info[a1.idatm_type].geometry except KeyError: print("1", file=f) continue try: geom2 = idatm_info[a2.idatm_type].geometry except KeyError: print("1", file=f) continue # sulfone/sulfoxide is classically depicted as double- # bonded despite the high dipolar character of the # bond making it have single-bond character. For # output, use the classical values. if sulfur_oxygen(a1) or sulfur_oxygen(a2): print("2", file=f) continue if geom1 not in [2, 3] or geom2 not in [2, 3]: print("1", file=f) continue # if either endpoint atom is in an aromatic ring and # the bond isn't, it's a single bond... for endp in [a1, a2]: aromatic = False for ring in endp.rings(): if ring.aromatic: aromatic = True break if aromatic: break else: # neither endpoint in aromatic ring if geom1 == 2 and geom2 == 2: print("3", file=f) else: print("2", file=f) continue print("1", file=f) if status: status("writing residues") # residue section header print("%s" % SUBSTR_HEADER, file=f) for i, res in enumerate(residues): # residue id field print("%6d" % (i + 1), end=" ", file=f) # residue name field if substructure_names: rname = substructure_names[res] elif res_num: rname = "%3s%-4d" % (res.name, res.number) else: rname = "%3s" % res.name print(rname, end=" ", file=f) # ID of the root atom of the residue chain_atom = res.principal_atom if chain_atom is None: # if writing out a selection, not all residue atoms # might be in atom_indices... for chain_atom in res.atoms: if chain_atom in atom_indices: break print("%5d" % atom_indices[chain_atom], end=" ", file=f) print("RESIDUE 4", end=" ", file=f) # Sybyl seems to use chain 'A' when chain ID is blank, # so run with that chain_id = res.chain_id if not chain_id.strip(): chain_id = 'A' print("%-4s %3s" % (chain_id, res.name), end=" ", file=f) # number of out-of-substructure bonds cross_res_bonds = 0 for a in res.atoms: for nb in a.neighbors: if nb.residue != res: cross_res_bonds += 1 print("%5d" % cross_res_bonds, end="", file=f) # print "ROOT" if first or only residue of a chain if not res.chain or res.chain.existing_residues[0] == res: print(" ROOT", file=f) else: print(file=f) # write flexible ligand docking info if anchor: if status: status("writing anchor info") print("%s" % SET_HEADER, file=f) atom_indices = {} for i, a in enumerate(atoms): atom_indices[a] = i + 1 bond_indices = {} for i, b in enumerate(bonds): bond_indices[b] = i + 1 print( "ANCHOR STATIC ATOMS <user> **** Anchor Atom Set", file=f) print(len(anchor), end=" ", file=f) for a in anchor: if a in atom_indices: print(atom_indices[a], end=" ", file=f) print(file=f) print( "RIGID STATIC BONDS <user> **** Rigid Bond Set", file=f) bonds = anchor.intra_bonds print(len(bonds), end=" ", file=f) for b in bonds: if b in bond_indices: print(bond_indices[b], end=" ", file=f) print(file=f) if file_name != f: f.close() if status: status("Wrote Mol2 file %s" % file_name)
def button_clicked(self, label): session = self.controller.session if label == self.record_label: from chimerax.ui.open_save import SaveDialog if self._record_dialog is None: fmt = session.data_formats["ChimeraX commands"] self._record_dialog = dlg = SaveDialog(session, self.window.ui_area, "Save Commands", data_formats=[fmt]) from PyQt5.QtWidgets import QFrame, QLabel, QHBoxLayout, QVBoxLayout, QComboBox from PyQt5.QtWidgets import QCheckBox from PyQt5.QtCore import Qt options_frame = dlg.custom_area options_layout = QVBoxLayout(options_frame) options_frame.setLayout(options_layout) amount_frame = QFrame(options_frame) options_layout.addWidget(amount_frame, Qt.AlignCenter) amount_layout = QHBoxLayout(amount_frame) amount_layout.addWidget(QLabel("Save", amount_frame)) self.save_amount_widget = saw = QComboBox(amount_frame) saw.addItems(["all", "selected"]) amount_layout.addWidget(saw) amount_layout.addWidget(QLabel("commands", amount_frame)) amount_frame.setLayout(amount_layout) self.append_checkbox = QCheckBox("Append to file", options_frame) self.append_checkbox.stateChanged.connect(self.append_changed) options_layout.addWidget(self.append_checkbox, Qt.AlignCenter) self.overwrite_disclaimer = disclaimer = QLabel( "<small><i>(ignore overwrite warning)</i></small>", options_frame) options_layout.addWidget(disclaimer, Qt.AlignCenter) disclaimer.hide() else: dlg = self._record_dialog if not dlg.exec(): return path = dlg.selectedFiles()[0] if not path: from chimerax.core.errors import UserError raise UserError("No file specified for saving command history") if self.save_amount_widget.currentText() == "all": cmds = [cmd for cmd in self.history()] else: # listbox.selectedItems() may not be in order, so... items = [ self.listbox.item(i) for i in range(self.listbox.count()) if self.listbox.item(i).isSelected() ] cmds = [item.text() for item in items] from chimerax.io import open_output f = open_output(path, encoding='utf-8', append=self.append_checkbox.isChecked()) for cmd in cmds: print(cmd, file=f) f.close() return if label == self.execute_label: for item in self.listbox.selectedItems(): self.controller.cmd_replace(item.text()) self.controller.execute() return if label == "Delete": retain = [] listbox_index = 0 for h_item in self._history: if self.typed_only and not h_item[1]: retain.append(h_item) continue if not self.listbox.item(listbox_index).isSelected(): # not selected for deletion retain.append(h_item) listbox_index += 1 self._history.replace(retain) self.populate() return if label == "Copy": clipboard = session.ui.clipboard() clipboard.setText("\n".join( [item.text() for item in self.listbox.selectedItems()])) return if label == "Help": from chimerax.core.commands import run run(session, 'help help:user/tools/cli.html#history') return
def _file_output(file_name, output_info, naming_style): inter_model, intra_model, relax_constraints, \ dist_slop, angle_slop, structures, hbond_info, cs_ids = output_info from chimerax.io import open_output out_file = open_output(file_name, 'utf-8') if inter_model: out_file.write("Finding intermodel H-bonds\n") if intra_model: out_file.write("Finding intramodel H-bonds\n") if relax_constraints: out_file.write("Constraints relaxed by %g angstroms" " and %d degrees\n" % (dist_slop, angle_slop)) else: out_file.write("Using precise constraint criteria\n") out_file.write("Models used:\n") for s in structures: out_file.write("\t%s %s\n" % (s.id_string, s.name)) if cs_ids is None: hbond_lists = [hbond_info] else: hbond_lists = hbond_info for i, hbonds in enumerate(hbond_lists): if cs_ids is None: cs_id = None else: cs_id = cs_ids[i] out_file.write("\nCoordinate set %d" % cs_id) out_file.write("\n%d H-bonds" % len(hbonds)) out_file.write( "\nH-bonds (donor, acceptor, hydrogen, D..A dist, D-H..A dist):\n") # want the bonds listed in some kind of consistent order... hbonds.sort() # figure out field widths to make things line up dwidth = awidth = hwidth = 0 labels = {} from chimerax.geometry import distance for don, acc in hbonds: if cs_id is None: don_coord = don.scene_coord acc_coord = acc.scene_coord else: don_coord = don.get_coordset_coord(cs_id) acc_coord = acc.get_coordset_coord(cs_id) labels[don] = don.string(style=naming_style) labels[acc] = acc.string(style=naming_style) dwidth = max(dwidth, len(labels[don])) awidth = max(awidth, len(labels[acc])) da = distance(don_coord, acc_coord) dha, hyd = donor_hyd(don, cs_id, acc_coord) if dha is None: dha_out = "N/A" hyd_out = "no hydrogen" else: dha_out = "%5.3f" % dha hyd_out = hyd.string(style=naming_style) hwidth = max(hwidth, len(hyd_out)) labels[(don, acc)] = (hyd_out, da, dha_out) for don, acc in hbonds: hyd_out, da, dha_out = labels[(don, acc)] out_file.write("%*s %*s %*s %5.3f %s\n" % (0 - dwidth, labels[don], 0 - awidth, labels[acc], 0 - hwidth, hyd_out, da, dha_out)) if out_file != file_name: # we opened it, so close it... out_file.close()