MayaChemTools

    1 #!/bin/env python
    2 #
    3 # File: PyMOLInfoMacromolecules.py
    4 # Author: Manish Sud <msud@san.rr.com>
    5 #
    6 # Copyright (C) 2026 Manish Sud. All rights reserved.
    7 #
    8 # The functionality available in this script is implemented using PyMOL, a
    9 # molecular visualization system on an open source foundation originally
   10 # developed by Warren DeLano.
   11 #
   12 # This file is part of MayaChemTools.
   13 #
   14 # MayaChemTools is free software; you can redistribute it and/or modify it under
   15 # the terms of the GNU Lesser General Public License as published by the Free
   16 # Software Foundation; either version 3 of the License, or (at your option) any
   17 # later version.
   18 #
   19 # MayaChemTools is distributed in the hope that it will be useful, but without
   20 # any warranty; without even the implied warranty of merchantability of fitness
   21 # for a particular purpose.  See the GNU Lesser General Public License for more
   22 # details.
   23 #
   24 # You should have received a copy of the GNU Lesser General Public License
   25 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or
   26 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330,
   27 # Boston, MA, 02111-1307, USA.
   28 #
   29 
   30 from __future__ import print_function
   31 
   32 import os
   33 import sys
   34 import time
   35 import re
   36 
   37 # PyMOL imports...
   38 try:
   39     import pymol
   40 
   41     # Finish launching PyMOL in  a command line mode for batch processing (-c)
   42     # along with the following options:  disable loading of pymolrc and plugins (-k);
   43     # suppress start up messages (-q)
   44     pymol.finish_launching(["pymol", "-ckq"])
   45 except ImportError as ErrMsg:
   46     sys.stderr.write("\nFailed to import PyMOL module/package: %s\n" % ErrMsg)
   47     sys.stderr.write("Check/update your PyMOL environment and try again.\n\n")
   48     sys.exit(1)
   49 
   50 # MayaChemTools imports...
   51 sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), "..", "lib", "Python"))
   52 try:
   53     from docopt import docopt
   54     import MiscUtil
   55     import PyMOLUtil
   56 except ImportError as ErrMsg:
   57     sys.stderr.write("\nFailed to import MayaChemTools module/package: %s\n" % ErrMsg)
   58     sys.stderr.write("Check/update your MayaChemTools environment and try again.\n\n")
   59     sys.exit(1)
   60 
   61 ScriptName = os.path.basename(sys.argv[0])
   62 Options = {}
   63 OptionsInfo = {}
   64 
   65 
   66 def main():
   67     """Start execution of the script."""
   68 
   69     MiscUtil.PrintInfo(
   70         "\n%s (PyMOL v%s; MayaChemTools v%s; %s): Starting...\n"
   71         % (ScriptName, pymol.cmd.get_version()[0], MiscUtil.GetMayaChemToolsVersion(), time.asctime())
   72     )
   73 
   74     (WallClockTime, ProcessorTime) = MiscUtil.GetWallClockAndProcessorTime()
   75 
   76     # Retrieve command line arguments and options...
   77     RetrieveOptions()
   78 
   79     # Process and validate command line arguments and options...
   80     ProcessOptions()
   81 
   82     # Perform actions required by the script...
   83     ListInfo()
   84 
   85     MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName)
   86     MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime))
   87 
   88 
   89 def ListInfo():
   90     """List information for macromolecules."""
   91 
   92     for Infile in OptionsInfo["InfilesNames"]:
   93         MiscUtil.PrintInfo("\nProcessing file %s..." % Infile)
   94         ListFileInfo(Infile)
   95 
   96 
   97 def ListFileInfo(Infile):
   98     """List information for macromolecules in a file."""
   99 
  100     FileDir, FileName, FileExt = MiscUtil.ParseFileName(Infile)
  101     MolName = FileName
  102 
  103     # Load infile...
  104     pymol.cmd.load(Infile, MolName)
  105 
  106     ChainIDs = PyMOLUtil.GetChains(MolName)
  107     ListHeaderInfo(Infile)
  108 
  109     ListChainsInfo(MolName, ChainIDs)
  110     ListChainsResiduesInfo(MolName, ChainIDs)
  111 
  112     ListLigandsInfo(MolName, ChainIDs)
  113     ListSolventsInfo(MolName, ChainIDs)
  114     ListInorganicsInfo(MolName, ChainIDs)
  115 
  116     ListPocketsInfo(MolName, ChainIDs)
  117 
  118     ListInterfaceResiduesInfo(MolName, ChainIDs)
  119     ListSurfaceResiduesInfo(MolName, ChainIDs)
  120     ListPhiPsiAnglesInfo(MolName, ChainIDs)
  121 
  122     ListBoundingBoxInfo(MolName)
  123     ListCentroid(MolName)
  124 
  125     # Delete infile object...
  126     pymol.cmd.delete(MolName)
  127 
  128     ListFileSizeAndModificationInfo(Infile)
  129 
  130 
  131 def ListHeaderInfo(Infile):
  132     """List header information."""
  133 
  134     if not (OptionsInfo["All"] or OptionsInfo["Header"]):
  135         return
  136 
  137     FileDir, FileName, FileExt = MiscUtil.ParseFileName(Infile)
  138 
  139     Classification, DepositionDate, IDCode, ExperimentalTechnique, Resolution = ["Not Available"] * 5
  140     if re.match("^pdb$", FileExt, re.I):
  141         Classification, DepositionDate, IDCode, ExperimentalTechnique, Resolution = (
  142             RetriveHeadAndExperimentalInfoFromPDBFile(Infile)
  143         )
  144     elif re.match("^cif$", FileExt, re.I):
  145         Classification, DepositionDate, IDCode, ExperimentalTechnique, Resolution = (
  146             RetriveHeadAndExperimentalInfoFromCIFFile(Infile)
  147         )
  148 
  149     MiscUtil.PrintInfo("\nID: %s\nClassification: %s\nDeposition date: %s" % (IDCode, Classification, DepositionDate))
  150     MiscUtil.PrintInfo("\nExperimental technique: %s\nResolution: %s" % (ExperimentalTechnique, Resolution))
  151 
  152 
  153 def RetriveHeadAndExperimentalInfoFromPDBFile(Infile):
  154     """Retrieve header and experimental information from PDB file."""
  155 
  156     Classification, DepositionDate, IDCode, ExperimentalTechnique, Resolution = ["Not Available"] * 5
  157 
  158     Lines = MiscUtil.GetTextLines(Infile)
  159 
  160     # Retrieve header info...
  161     for Line in Lines:
  162         if re.match("^HEADER", Line, re.I):
  163             # Format: 10x40s9s3x4s
  164             FormatSize = 66
  165             Line = PrepareLineForFormatSize(Line, FormatSize)
  166 
  167             Classification = Line[10:50]
  168             DepositionDate = Line[50:59]
  169             IDCode = Line[62:66]
  170 
  171             Classification = Classification.strip() if len(Classification.strip()) else "Not Available"
  172             DepositionDate = DepositionDate.strip() if len(DepositionDate.strip()) else "Not Available"
  173             IDCode = IDCode.strip() if len(IDCode.strip()) else "Not Available"
  174 
  175             break
  176 
  177     # Retrieve experimental info...
  178     for Line in Lines:
  179         if re.match("^EXPDTA", Line, re.I):
  180             ExperimentalTechnique = re.sub("^EXPDTA", "", Line, flags=re.I)
  181             ExperimentalTechnique = ExperimentalTechnique.strip()
  182         elif re.match("^REMARK   2 RESOLUTION.", Line, re.I):
  183             if re.search("NOT APPLICABLE", Line, re.I):
  184                 Resolution = "NOT APPLICABLE"
  185             else:
  186                 FormatSize = 70
  187                 Line = PrepareLineForFormatSize(Line, FormatSize)
  188                 Resolution = Line[22:70]
  189                 Resolution = Resolution.strip() if len(Resolution.strip()) else "Not Available"
  190         elif re.match("^(ATOM|HETATM)", Line, re.I):
  191             break
  192 
  193     return Classification, DepositionDate, IDCode, ExperimentalTechnique, Resolution
  194 
  195 
  196 def RetriveHeadAndExperimentalInfoFromCIFFile(Infile):
  197     """Retrieve header information from CIF file."""
  198 
  199     Classification, DepositionDate, IDCode, ExperimentalTechnique, Resolution = ["Not Available"] * 5
  200 
  201     Lines = MiscUtil.GetTextLines(Infile)
  202 
  203     # IDCode...
  204     for Line in Lines:
  205         if re.match("^_struct_keywords.entry_id", Line, re.I):
  206             IDCode = re.sub("^_struct_keywords.entry_id", "", Line, flags=re.I)
  207             IDCode = IDCode.strip() if len(IDCode.strip()) else "Not Available"
  208             break
  209 
  210     # Classification...
  211     for Line in Lines:
  212         if re.match("^_struct_keywords.pdbx_keywords", Line, re.I):
  213             Classification = re.sub("^_struct_keywords.pdbx_keywords", "", Line, flags=re.I)
  214             Classification = Classification.strip() if len(Classification.strip()) else "Not Available"
  215             break
  216 
  217     # Deposition date...
  218     for Line in Lines:
  219         if re.match("^_pdbx_database_status.recvd_initial_deposition_date", Line, re.I):
  220             DepositionDate = re.sub("^_pdbx_database_status.recvd_initial_deposition_date", "", Line, flags=re.I)
  221             DepositionDate = DepositionDate.strip() if len(DepositionDate.strip()) else "Not Available"
  222             break
  223 
  224     # Experimental technique...
  225     for Line in Lines:
  226         if re.match("^_exptl.method", Line, re.I):
  227             ExperimentalTechnique = re.sub("(_exptl.method|')", "", Line, flags=re.I)
  228             ExperimentalTechnique = (
  229                 ExperimentalTechnique.strip() if len(ExperimentalTechnique.strip()) else "Not Available"
  230             )
  231             break
  232 
  233     # Resolution...
  234     for Line in Lines:
  235         if re.match("^_reflns.d_resolution_high", Line, re.I):
  236             Resolution = re.sub("^_reflns.d_resolution_high", "", Line, flags=re.I)
  237             Resolution = Resolution.strip() if len(Resolution.strip()) else "Not Available"
  238             break
  239 
  240     return Classification, DepositionDate, IDCode, ExperimentalTechnique, Resolution
  241 
  242 
  243 def PrepareLineForFormatSize(Text, FormatSize):
  244     """Prepare text string for format size padding or truncation to
  245     alter its size.
  246     """
  247 
  248     TextLen = len(Text)
  249     if TextLen < FormatSize:
  250         PaddingLen = FormatSize - TextLen
  251         TextPadding = " " * PaddingLen
  252         Text = Text + TextPadding
  253     elif TextLen > FormatSize:
  254         Text = Text[:FormatSize]
  255 
  256     return Text
  257 
  258 
  259 def ListChainsInfo(MolName, ChainIDs):
  260     """List chains information across all chains."""
  261 
  262     if not (OptionsInfo["All"] or OptionsInfo["Chains"]):
  263         return
  264 
  265     ChainsInfo = ", ".join(ChainIDs) if len(ChainIDs) else "None"
  266 
  267     MiscUtil.PrintInfo("\nNumber of chains: %s" % len(ChainIDs))
  268     MiscUtil.PrintInfo("ChainIDs: %s" % ChainsInfo)
  269 
  270 
  271 def ListChainsResiduesInfo(MolName, ChainIDs):
  272     """List polymer chain residue information across all chains."""
  273 
  274     if not (OptionsInfo["All"] or OptionsInfo["CountResidues"]):
  275         return
  276 
  277     ListSelectionResiduesInfo(MolName, ChainIDs, "Chains")
  278 
  279     # List information for non-standard amino acids...
  280     ListSelectionResiduesInfo(MolName, ChainIDs, "NonStandardAminoAcids")
  281 
  282 
  283 def ListLigandsInfo(MolName, ChainIDs):
  284     """List ligand information across all chains."""
  285 
  286     if not (OptionsInfo["All"] or OptionsInfo["Ligands"]):
  287         return
  288 
  289     ListSelectionResiduesInfo(MolName, ChainIDs, "Ligands")
  290 
  291 
  292 def ListSolventsInfo(MolName, ChainIDs):
  293     """List solvents information across all chains."""
  294 
  295     if not (OptionsInfo["All"] or OptionsInfo["Solvents"]):
  296         return
  297 
  298     ListSelectionResiduesInfo(MolName, ChainIDs, "Solvents")
  299 
  300 
  301 def ListInorganicsInfo(MolName, ChainIDs):
  302     """List inorganics information across all chains."""
  303 
  304     if not (OptionsInfo["All"] or OptionsInfo["Inorganics"]):
  305         return
  306 
  307     ListSelectionResiduesInfo(MolName, ChainIDs, "Inorganics")
  308 
  309 
  310 def ListSelectionResiduesInfo(MolName, ChainIDs, SelectionType):
  311     """List residues information for a specified selection type."""
  312 
  313     Lines = []
  314     TotalResCount = 0
  315     SelectionLabel = None
  316 
  317     if not len(ChainIDs):
  318         SelectionInfo, SelectionLabel = GetSelectionResiduesInfo(MolName, None, SelectionType)
  319         MiscUtil.PrintInfo("\nNumber of %s residues: %s" % (SelectionLabel, TotalResCount))
  320         return
  321 
  322     for ChainID in ChainIDs:
  323         SelectionInfo, SelectionLabel = GetSelectionResiduesInfo(MolName, ChainID, SelectionType)
  324 
  325         ChainResCount = 0
  326         LineWords = []
  327 
  328         SortedResNames = sorted(
  329             SelectionInfo["ResNames"], key=lambda ResName: SelectionInfo["ResCount"][ResName], reverse=True
  330         )
  331         for ResName in SortedResNames:
  332             ResCount = SelectionInfo["ResCount"][ResName]
  333             LineWords.append("%s - %s" % (ResName, ResCount))
  334 
  335             ChainResCount += ResCount
  336             TotalResCount += ResCount
  337 
  338         Line = "; ".join(LineWords) if len(LineWords) else None
  339         Lines.append("Chain ID: %s; Count: %s; Names:  %s" % (ChainID, ChainResCount, Line))
  340 
  341     MiscUtil.PrintInfo("\nNumber of %s residues: %s" % (SelectionLabel, TotalResCount))
  342     for Line in Lines:
  343         MiscUtil.PrintInfo("%s" % Line)
  344 
  345 
  346 def GetSelectionResiduesInfo(MolName, ChainID, SelectionType):
  347     """Get residues info for a specified selection type."""
  348 
  349     SelectionInfo = None
  350     SelectionLabel = None
  351 
  352     if re.match("^Ligands$", SelectionType, re.I):
  353         SelectionLabel = "ligand"
  354         SelectionInfo = PyMOLUtil.GetLigandResiduesInfo(MolName, ChainID) if ChainID is not None else None
  355     elif re.match("^Solvents$", SelectionType, re.I):
  356         SelectionLabel = "solvent"
  357         SelectionInfo = PyMOLUtil.GetSolventResiduesInfo(MolName, ChainID) if ChainID is not None else None
  358     elif re.match("^Inorganics$", SelectionType, re.I):
  359         SelectionLabel = "inorganic"
  360         SelectionInfo = PyMOLUtil.GetInorganicResiduesInfo(MolName, ChainID) if ChainID is not None else None
  361     elif re.match("^Chains$", SelectionType, re.I):
  362         SelectionLabel = "polymer chain"
  363         SelectionInfo = PyMOLUtil.GetPolymerResiduesInfo(MolName, ChainID) if ChainID is not None else None
  364     elif re.match("^NonStandardAminoAcids$", SelectionType, re.I):
  365         SelectionLabel = "non-standard amino acids"
  366         SelectionInfo = (
  367             PyMOLUtil.GetAminoAcidResiduesInfo(MolName, ChainID, "NonStandard") if ChainID is not None else None
  368         )
  369     else:
  370         MiscUtil.PrintError(
  371             "Failed to retrieve residues information: Selection type %s is not valid..." % SelectionType
  372         )
  373 
  374     return SelectionInfo, SelectionLabel
  375 
  376 
  377 def ListPocketsInfo(MolName, ChainIDs):
  378     """List pockect residues information across all chains."""
  379 
  380     if not (
  381         OptionsInfo["All"]
  382         or OptionsInfo["PocketLigands"]
  383         or OptionsInfo["PocketSolvents"]
  384         or OptionsInfo["PocketInorganics"]
  385     ):
  386         return
  387 
  388     for ChainID in ChainIDs:
  389         MiscUtil.PrintInfo("\nListing ligand pockets information for chain %s..." % (ChainID))
  390 
  391         LigandsInfo = PyMOLUtil.GetLigandResiduesInfo(MolName, ChainID)
  392         if not len(LigandsInfo["ResNames"]):
  393             MiscUtil.PrintInfo("\nNumber of residues in ligand pocket: None")
  394             MiscUtil.PrintInfo("Chain ID: %s; Ligands: None" % (ChainID))
  395             continue
  396 
  397         for LigandResName in sorted(LigandsInfo["ResNames"]):
  398             for LigandResNum in LigandsInfo["ResNum"][LigandResName]:
  399                 ListPocketsPolymerInfo(MolName, ChainID, LigandResName, LigandResNum)
  400                 ListPocketsSolventsInfo(MolName, ChainID, LigandResName, LigandResNum)
  401                 ListPocketsInorganicsInfo(MolName, ChainID, LigandResName, LigandResNum)
  402 
  403 
  404 def ListPocketsPolymerInfo(MolName, ChainID, LigandResName, LigandResNum):
  405     """List pockect residues information across all chains."""
  406 
  407     if not (OptionsInfo["All"] or OptionsInfo["PocketLigands"]):
  408         return
  409 
  410     ListPocketSelectionResiduesInfo(MolName, ChainID, LigandResName, LigandResNum, "Pockets")
  411 
  412 
  413 def ListPocketsSolventsInfo(MolName, ChainID, LigandResName, LigandResNum):
  414     """List pockect solvent residues information across all chains."""
  415 
  416     if not (OptionsInfo["All"] or OptionsInfo["PocketSolvents"]):
  417         return
  418 
  419     ListPocketSelectionResiduesInfo(MolName, ChainID, LigandResName, LigandResNum, "PocketSolvents")
  420 
  421 
  422 def ListPocketsInorganicsInfo(MolName, ChainID, LigandResName, LigandResNum):
  423     """List pockect inorganic residues information across all chains."""
  424 
  425     if not (OptionsInfo["All"] or OptionsInfo["PocketInorganics"]):
  426         return
  427 
  428     ListPocketSelectionResiduesInfo(MolName, ChainID, LigandResName, LigandResNum, "PocketInorganics")
  429 
  430 
  431 def ListPocketSelectionResiduesInfo(MolName, ChainID, LigandResName, LigandResNum, SelectionType):
  432     """List residues information for a specified pocket selection type."""
  433 
  434     PocketDistanceCutoff = OptionsInfo["PocketDistanceCutoff"]
  435     SelectionLabel = GetPocketSelectionResiduesInfoLabel(SelectionType)
  436 
  437     SelectionInfo = GetPocketSelectionResiduesInfo(
  438         MolName, ChainID, LigandResName, LigandResNum, PocketDistanceCutoff, SelectionType
  439     )
  440 
  441     ResiduesCount, ResiduesDistribution, ResiduesIDs = SetupSelectionResiduesInfo(SelectionInfo)
  442     MiscUtil.PrintInfo("\nNumber of %s residues in ligand pocket: %s" % (SelectionLabel, ResiduesCount))
  443     MiscUtil.PrintInfo(
  444         "Chain ID: %s; Ligand ID: %s_%s\nResidue distribution: %s\nResidue IDs: %s"
  445         % (ChainID, LigandResName, LigandResNum, ResiduesDistribution, ResiduesIDs)
  446     )
  447 
  448 
  449 def GetPocketSelectionResiduesInfo(MolName, ChainID, LigandResName, LigandResNum, PocketDistanceCutoff, SelectionType):
  450     """Get pocket residues info for a specified selection type."""
  451 
  452     SelectionInfo = None
  453 
  454     if re.match("^Pockets$", SelectionType, re.I):
  455         SelectionInfo = PyMOLUtil.GetPocketPolymerResiduesInfo(
  456             MolName, ChainID, LigandResName, LigandResNum, PocketDistanceCutoff
  457         )
  458     elif re.match("^PocketSolvents$", SelectionType, re.I):
  459         SelectionInfo = PyMOLUtil.GetPocketSolventResiduesInfo(
  460             MolName, ChainID, LigandResName, LigandResNum, PocketDistanceCutoff
  461         )
  462     elif re.match("^PocketInorganics$", SelectionType, re.I):
  463         SelectionInfo = PyMOLUtil.GetPocketInorganicResiduesInfo(
  464             MolName, ChainID, LigandResName, LigandResNum, PocketDistanceCutoff
  465         )
  466     else:
  467         MiscUtil.PrintError(
  468             "Failed to retrieve pocket residues information: Selection type %s is not valid..." % SelectionType
  469         )
  470 
  471     return SelectionInfo
  472 
  473 
  474 def ListSurfaceResiduesInfo(MolName, ChainIDs):
  475     """List surface and buried residues for polymer chains with in a molecule."""
  476 
  477     if not (OptionsInfo["All"] or OptionsInfo["SurfaceResidues"]):
  478         return
  479 
  480     MiscUtil.PrintInfo("\nListing surface and buried residues information...")
  481     if not len(ChainIDs):
  482         MiscUtil.PrintInfo("\nNumber of surface residues: None\nNumber of buried residues: None")
  483         return
  484 
  485     TotalSurfaceResidues, TotalBuriedResidues = [0] * 2
  486 
  487     for ChainID in ChainIDs:
  488         SurfaceResiduesSelectionInfo, BuriedResiduesSelectionInfo = PyMOLUtil.GetSurfaceAndBuriedResiduesInfo(
  489             MolName, ChainID, OptionsInfo["SurfaceResiduesCutoff"]
  490         )
  491 
  492         SurfaceResiduesCount, SurfaceResiduesDistribution, SurfaceResiduesIDs = SetupSelectionResiduesInfo(
  493             SurfaceResiduesSelectionInfo
  494         )
  495         BuriedResiduesCount, BuriedResiduesDistribution, BuriedResiduesIDs = SetupSelectionResiduesInfo(
  496             BuriedResiduesSelectionInfo
  497         )
  498 
  499         TotalSurfaceResidues += SurfaceResiduesCount
  500         MiscUtil.PrintInfo("\nChainID: %s; Number of surface residues: %d" % (ChainID, SurfaceResiduesCount))
  501         MiscUtil.PrintInfo("Residue distribution: %s" % (SurfaceResiduesDistribution))
  502         if OptionsInfo["SurfaceResiduesIDs"]:
  503             MiscUtil.PrintInfo("Residue IDs: %s" % (SurfaceResiduesIDs))
  504 
  505         TotalBuriedResidues += BuriedResiduesCount
  506         MiscUtil.PrintInfo("\nChainID: %s; Number of buried residues: %d" % (ChainID, BuriedResiduesCount))
  507         MiscUtil.PrintInfo("Residue distribution: %s" % (BuriedResiduesDistribution))
  508         if OptionsInfo["SurfaceResiduesIDs"]:
  509             MiscUtil.PrintInfo("Residue IDs: %s" % (BuriedResiduesIDs))
  510 
  511     MiscUtil.PrintInfo(
  512         "\nTotal number of surface residues: %d\nTotal number of buried residues: %s"
  513         % (TotalSurfaceResidues, TotalBuriedResidues)
  514     )
  515 
  516 
  517 def ListPhiPsiAnglesInfo(MolName, ChainIDs):
  518     """List phi and psi torsion angles for polymer chains with in a molecule."""
  519 
  520     if not (OptionsInfo["All"] or OptionsInfo["PhiPsi"]):
  521         return
  522 
  523     MiscUtil.PrintInfo("\nListing phi and psi angles information...")
  524 
  525     if not len(ChainIDs):
  526         MiscUtil.PrintInfo("\nNumber of phi and psi angles: None\n")
  527         return
  528 
  529     for ChainID in ChainIDs:
  530         if re.match("^Categories$", OptionsInfo["PhiPsiMode"], re.I):
  531             # Retrieve phi and psi angles by categories used for Ramachandran plots
  532             GeneralPhiPsiInfo, GlyPhiPsiInfo, ProPhiPsiInfo, PreProPhiPsiInfo = (
  533                 PyMOLUtil.GetPhiPsiCategoriesResiduesInfo(MolName, ChainID)
  534             )
  535 
  536             SetupAndListPhiPsiResiduesInfo(
  537                 GeneralPhiPsiInfo, "General (All residues except glycine, proline, or pre-proline)", ChainID
  538             )
  539             SetupAndListPhiPsiResiduesInfo(GlyPhiPsiInfo, "Glycine (Only glycine residues)", ChainID)
  540             SetupAndListPhiPsiResiduesInfo(ProPhiPsiInfo, "Proline (Only proline residues)", ChainID)
  541             SetupAndListPhiPsiResiduesInfo(
  542                 PreProPhiPsiInfo, "Pre-Proline (Only residues before proline not including glycine or proline)", ChainID
  543             )
  544         else:
  545             PhiPsiResiduesInfo = PyMOLUtil.GetPhiPsiResiduesInfo(MolName, ChainID, Categorize=True)
  546             SetupAndListPhiPsiResiduesInfo(PhiPsiResiduesInfo, "All", ChainID)
  547 
  548 
  549 def SetupAndListPhiPsiResiduesInfo(PhiPsiResiduesInfo, AnglesType, ChainID):
  550     """Setup and  list information for phi and psi torsion angles."""
  551 
  552     MiscUtil.PrintInfo("\nChainID: %s; Phi and Psi angles: %s" % (ChainID, AnglesType))
  553 
  554     PhiPsiCount = len(PhiPsiResiduesInfo["ResNums"])
  555     if not PhiPsiCount:
  556         MiscUtil.PrintInfo("Number of angles: None")
  557         return
  558 
  559     MiscUtil.PrintInfo("Number of phi and psi angles: %d" % PhiPsiCount)
  560 
  561     CategoriesMode = True if re.match("^Categories$", OptionsInfo["PhiPsiMode"], re.I) else False
  562 
  563     if CategoriesMode:
  564         MiscUtil.PrintInfo("\nResNum, ResName, Phi, Psi")
  565     else:
  566         MiscUtil.PrintInfo("\nResNum, ResName, Phi, Psi, Category")
  567 
  568     Precision = OptionsInfo["PhiPsiPrecision"]
  569     for ResNum in PhiPsiResiduesInfo["ResNums"]:
  570         ResName = PhiPsiResiduesInfo["ResName"][ResNum]
  571         Phi = PhiPsiResiduesInfo["Phi"][ResNum]
  572         Psi = PhiPsiResiduesInfo["Psi"][ResNum]
  573         if CategoriesMode:
  574             MiscUtil.PrintInfo("%s, %s, %.*f, %.*f" % (ResNum, ResName, Precision, Phi, Precision, Psi))
  575         else:
  576             Category = PhiPsiResiduesInfo["Category"][ResNum]
  577             MiscUtil.PrintInfo("%s, %s, %.*f, %.*f, %s " % (ResNum, ResName, Precision, Phi, Precision, Psi, Category))
  578 
  579 
  580 def ListInterfaceResiduesInfo(MolName, ChainIDs):
  581     """List interface residues between all unique pairs of polymer chains
  582     with in a molecule."""
  583 
  584     if not (OptionsInfo["All"] or OptionsInfo["InterfaceResidues"]):
  585         return
  586 
  587     MiscUtil.PrintInfo("\nListing interface residues information...")
  588 
  589     # Get chain ID pairs for identifying interface residues...
  590     ChainIDsPairs = GetChainIDsPairsForInterfaceResidues(ChainIDs)
  591     if not len(ChainIDsPairs):
  592         MiscUtil.PrintInfo("Valid interface chain ID pairs: None")
  593         return
  594 
  595     InterfaceResiduesMethod = OptionsInfo["InterfaceResiduesMethod"]
  596     InterfaceResiduesCutoff = OptionsInfo["InterfaceResiduesCutoff"]
  597     MiscUtil.PrintInfo("Methodology: %s; Cutoff: %.2f" % (InterfaceResiduesMethod, InterfaceResiduesCutoff))
  598 
  599     for ChainIDsPair in ChainIDsPairs:
  600         InterfaceChainIDs1, InterfaceChainIDs2 = ChainIDsPair
  601         InterfaceID = "%s-%s" % ("+".join(InterfaceChainIDs1), "+".join(InterfaceChainIDs2))
  602 
  603         InterfaceResiduesInfo1, InterfaceResiduesInfo2 = GetInterfaceChainsAndResiduesInfo(
  604             MolName, InterfaceChainIDs1, MolName, InterfaceChainIDs2, InterfaceResiduesMethod, InterfaceResiduesCutoff
  605         )
  606 
  607         ChainNames1, ResiduesInfo1 = SetupSelectionChainsResiduesInfo(InterfaceResiduesInfo1)
  608         ChainNames2, ResiduesInfo2 = SetupSelectionChainsResiduesInfo(InterfaceResiduesInfo2)
  609 
  610         if len(ChainNames1) and len(ChainNames2):
  611             MiscUtil.PrintInfo("\nListing interface residues for interface chain IDs: %s" % (InterfaceID))
  612 
  613             ListInterfaceChainsAndResiduesInfo(InterfaceID, InterfaceChainIDs1, ChainNames1, ResiduesInfo1)
  614             ListInterfaceChainsAndResiduesInfo(InterfaceID, InterfaceChainIDs2, ChainNames2, ResiduesInfo2)
  615         else:
  616             MiscUtil.PrintInfo("\nListing interface residues for interface chain IDs: %s" % (InterfaceID))
  617             MiscUtil.PrintInfo("Interface chain IDs: None; ChainID: None; Number of interface residues: 0")
  618 
  619 
  620 def ListInterfaceChainsAndResiduesInfo(InterfaceID, InterfaceChainIDs, ChainNames, ResiduesInfo):
  621     """List interface chains and residues."""
  622 
  623     for ChainID in InterfaceChainIDs:
  624         if ChainID not in ChainNames:
  625             MiscUtil.PrintInfo(
  626                 "\nInterface chain IDs: %s; ChainID: %s; Number of interface residues: 0" % (InterfaceID, ChainID)
  627             )
  628             continue
  629 
  630         for Index in range(0, len(ChainNames)):
  631             ChainName = ChainNames[Index]
  632             ChainResiduesInfo = ResiduesInfo[Index]
  633             if re.match(ChainID, ChainName, re.I):
  634                 MiscUtil.PrintInfo(
  635                     "\nInterface chain IDs: %s; ChainID: %s; Number of interface residues: %d"
  636                     % (InterfaceID, ChainName, ChainResiduesInfo[0])
  637                 )
  638                 MiscUtil.PrintInfo(
  639                     "Residue distribution: %s\nResidue IDs: %s" % (ChainResiduesInfo[1], ChainResiduesInfo[2])
  640                 )
  641                 continue
  642 
  643 
  644 def GetInterfaceChainsAndResiduesInfo(MolName1, ChainIDs1, MolName2, ChainIDs2, Method, Cutoff):
  645     """Get interface chains and residues info for chains using a specified methodology."""
  646 
  647     InterfaceChainsResiduesInfo1 = None
  648     InterfaceChainsResiduesInfo2 = None
  649 
  650     ChainNames1 = ",".join(ChainIDs1)
  651     ChainNames2 = ",".join(ChainIDs2)
  652 
  653     if re.match("^BySASAChange$", Method, re.I):
  654         InterfaceChainsResiduesInfo1, InterfaceChainsResiduesInfo2 = PyMOLUtil.GetInterfaceChainsResiduesBySASAChange(
  655             MolName1, ChainNames1, MolName2, ChainNames2, Cutoff
  656         )
  657     elif re.match("^ByHeavyAtomsDistance$", Method, re.I):
  658         InterfaceChainsResiduesInfo1, InterfaceChainsResiduesInfo2 = (
  659             PyMOLUtil.GetInterfaceChainsResiduesByHeavyAtomsDistance(
  660                 MolName1, ChainNames1, MolName2, ChainNames2, Cutoff
  661             )
  662         )
  663     elif re.match("^ByCAlphaAtomsDistance$", Method, re.I):
  664         InterfaceChainsResiduesInfo1, InterfaceChainsResiduesInfo2 = (
  665             PyMOLUtil.GetInterfaceChainsResiduesByCAlphaAtomsDistance(
  666                 MolName1, ChainNames1, MolName2, ChainNames2, Cutoff
  667             )
  668         )
  669     else:
  670         MiscUtil.PrintError("Failed to retrieve interface residues information: Method %s is not valid..." % Method)
  671 
  672     return InterfaceChainsResiduesInfo1, InterfaceChainsResiduesInfo2
  673 
  674 
  675 def GetChainIDsPairsForInterfaceResidues(ChainIDs):
  676     """Get chain IDs pairs for identifying interface residues."""
  677 
  678     ChainIDsPairsList = []
  679 
  680     InterfaceResiduesChainsList = OptionsInfo["InterfaceResiduesChainsList"]
  681     if not len(InterfaceResiduesChainsList):
  682         # Use first two chain IDs...
  683         if len(ChainIDs) >= 2:
  684             ChainIDsPair = [ChainIDs[0], ChainIDs[1]]
  685             ChainIDsPairsList.append(ChainIDsPair)
  686         return ChainIDsPairsList
  687 
  688     # Validate specified pairwise chain IDs...
  689     for Index in range(0, len(InterfaceResiduesChainsList), 2):
  690         ChainIDs1 = InterfaceResiduesChainsList[Index]
  691         ChainIDs2 = InterfaceResiduesChainsList[Index + 1]
  692 
  693         ValidChainIDs = True
  694         SpecifiedChainIDs = []
  695         SpecifiedChainIDs.extend(ChainIDs1)
  696         SpecifiedChainIDs.extend(ChainIDs2)
  697 
  698         for ChainID in SpecifiedChainIDs:
  699             if ChainID not in ChainIDs:
  700                 ValidChainIDs = False
  701                 MiscUtil.PrintWarning(
  702                     'The chain ID, %s, specified using "--interfaceResiduesChains" for a chain IDs pairs is not a valid chain ID.'
  703                     % (ChainID)
  704                 )
  705 
  706         if not ValidChainIDs:
  707             MiscUtil.PrintWarning("Ignoring chain IDs pair: %s, %s" % ("+".join(ChainIDs1), "+".join(ChainIDs2)))
  708             continue
  709 
  710         ChainIDsPair = [ChainIDs1, ChainIDs2]
  711         ChainIDsPairsList.append(ChainIDsPair)
  712 
  713     return ChainIDsPairsList
  714 
  715 
  716 def SetupSelectionResiduesInfo(SelectionInfo):
  717     """Setup residues info."""
  718 
  719     # Setup distribution of residues...
  720     LineWords = []
  721     ResiduesCount = 0
  722     SortedResNames = sorted(
  723         SelectionInfo["ResNames"], key=lambda ResName: SelectionInfo["ResCount"][ResName], reverse=True
  724     )
  725     for ResName in SortedResNames:
  726         ResCount = SelectionInfo["ResCount"][ResName]
  727         LineWords.append("%s - %s" % (ResName, ResCount))
  728         ResiduesCount += ResCount
  729 
  730     ResiduesDistribution = "; ".join(LineWords) if len(LineWords) else None
  731 
  732     # Setup residue IDs sorted by residue numbers...
  733     ResNumMap = {}
  734     for ResName in SelectionInfo["ResNames"]:
  735         for ResNum in SelectionInfo["ResNum"][ResName]:
  736             ResNumMap[ResNum] = ResName
  737 
  738     LineWords = []
  739     for ResNum in sorted(ResNumMap, key=int):
  740         ResName = ResNumMap[ResNum]
  741         ResID = "%s_%s" % (ResName, ResNum)
  742         LineWords.append(ResID)
  743     ResiduesIDs = ", ".join(LineWords) if len(LineWords) else None
  744 
  745     return ResiduesCount, ResiduesDistribution, ResiduesIDs
  746 
  747 
  748 def SetupSelectionChainsResiduesInfo(SelectionInfo):
  749     """Setup chains and residues info."""
  750 
  751     ChainNames = []
  752     ResiduesInfo = []
  753 
  754     for ChainID in SelectionInfo["ChainIDs"]:
  755         ChainNames.append(ChainID)
  756 
  757         # Setup distribution of residues...
  758         LineWords = []
  759         ResiduesCount = 0
  760         SortedResNames = sorted(
  761             SelectionInfo["ResNames"][ChainID],
  762             key=lambda ResName: SelectionInfo["ResCount"][ChainID][ResName],
  763             reverse=True,
  764         )
  765         for ResName in SortedResNames:
  766             ResCount = SelectionInfo["ResCount"][ChainID][ResName]
  767             LineWords.append("%s - %s" % (ResName, ResCount))
  768             ResiduesCount += ResCount
  769 
  770         ResiduesDistribution = "; ".join(LineWords) if len(LineWords) else None
  771 
  772         # Setup residue IDs sorted by residue numbers...
  773         ResNumMap = {}
  774         for ResName in SelectionInfo["ResNames"][ChainID]:
  775             for ResNum in SelectionInfo["ResNum"][ChainID][ResName]:
  776                 ResNumMap[ResNum] = ResName
  777 
  778         LineWords = []
  779         for ResNum in sorted(ResNumMap, key=int):
  780             ResName = ResNumMap[ResNum]
  781             ResID = "%s_%s" % (ResName, ResNum)
  782             LineWords.append(ResID)
  783         ResiduesIDs = ", ".join(LineWords) if len(LineWords) else None
  784 
  785         ResiduesInfo.append([ResiduesCount, ResiduesDistribution, ResiduesIDs])
  786 
  787     return ChainNames, ResiduesInfo
  788 
  789 
  790 def ListBoundingBoxInfo(MolName):
  791     """List bounding box information."""
  792 
  793     if not (OptionsInfo["All"] or OptionsInfo["BoundingBox"]):
  794         return
  795 
  796     MolSelection = "(%s)" % MolName
  797     MolExtents = pymol.cmd.get_extent(MolSelection)
  798 
  799     XMin, YMin, ZMin = MolExtents[0]
  800     XMax, YMax, ZMax = MolExtents[1]
  801 
  802     XSize = abs(XMax - XMin)
  803     YSize = abs(YMax - YMin)
  804     ZSize = abs(ZMax - ZMin)
  805 
  806     MiscUtil.PrintInfo(
  807         "\nBounding box coordinates: <XMin, XMax> - <%.3f, %.3f>; <YMin, YMax> - <%.3f, %.3f>; <ZMin, ZMax> - <%.3f, %.3f>"
  808         % (XMin, XMax, YMin, YMax, ZMin, ZMax)
  809     )
  810     MiscUtil.PrintInfo(
  811         "Bounding box size in Angstroms: XSize - %.3f; YSize - %.3f; ZSize - %.3f" % (XSize, YSize, ZSize)
  812     )
  813 
  814 
  815 def ListCentroid(MolName):
  816     """List centroid information."""
  817 
  818     if not (OptionsInfo["All"] or OptionsInfo["Centroid"]):
  819         return
  820 
  821     MolSelection = "(%s)" % MolName
  822     Centroid = PyMOLUtil.GetCentroid(MolSelection)
  823 
  824     MiscUtil.PrintInfo("\nCentroid: <%.3f, %.3f, %.3f>" % (Centroid[0], Centroid[1], Centroid[2]))
  825 
  826 
  827 def ListFileSizeAndModificationInfo(Infile):
  828     """List file size and modification time info."""
  829 
  830     MiscUtil.PrintInfo("\nFile size: %s" % MiscUtil.GetFormattedFileSize(Infile))
  831     MiscUtil.PrintInfo("Last modified: %s" % time.ctime(os.path.getmtime(Infile)))
  832     MiscUtil.PrintInfo("Created: %s" % time.ctime(os.path.getctime(Infile)))
  833 
  834 
  835 def GetPocketSelectionResiduesInfoLabel(SelectionType):
  836     """Get pocket residues info label for a specified selection type."""
  837 
  838     SelectionLabel = None
  839 
  840     if re.match("^Pockets$", SelectionType, re.I):
  841         SelectionLabel = "polymer"
  842     elif re.match("^PocketSolvents$", SelectionType, re.I):
  843         SelectionLabel = "solvent"
  844     elif re.match("^PocketInorganics$", SelectionType, re.I):
  845         SelectionLabel = "inorganic"
  846     else:
  847         MiscUtil.PrintError(
  848             "Failed to retrieve pocket residues label information: Selection type %s is not valid..." % SelectionType
  849         )
  850 
  851     return SelectionLabel
  852 
  853 
  854 def ProcessOptions():
  855     """Process and validate command line arguments and options."""
  856 
  857     MiscUtil.PrintInfo("Processing options...")
  858 
  859     # Validate options...
  860     ValidateOptions()
  861 
  862     OptionsInfo["All"] = Options["--all"]
  863     OptionsInfo["BoundingBox"] = Options["--boundingBox"]
  864     OptionsInfo["Centroid"] = Options["--centroid"]
  865 
  866     OptionsInfo["Chains"] = Options["--chains"]
  867 
  868     OptionsInfo["CountResidues"] = Options["--countResidues"]
  869     OptionsInfo["Header"] = Options["--header"]
  870 
  871     OptionsInfo["Infiles"] = Options["--infiles"]
  872     OptionsInfo["InfilesNames"] = Options["--infilesNames"]
  873 
  874     OptionsInfo["Inorganics"] = Options["--inorganics"]
  875 
  876     OptionsInfo["InterfaceResidues"] = Options["--interfaceResidues"]
  877     OptionsInfo["InterfaceResiduesMethod"] = Options["--interfaceResiduesMethod"]
  878 
  879     InterfaceResiduesChains = Options["--interfaceResiduesChains"]
  880     InterfaceResiduesChainsList = []
  881     if not re.match("^auto$", InterfaceResiduesChains, re.I):
  882         InterfaceResiduesChains = re.sub(" ", "", Options["--interfaceResiduesChains"])
  883         InterfaceResiduesChainsWords = InterfaceResiduesChains.split(",")
  884         if len(InterfaceResiduesChainsWords) % 2:
  885             MiscUtil.PrintError(
  886                 'The number of comma delimited chain IDs, %d, specified using "--interfaceResiduesChains" option, "%s",  must be a multple of 2.'
  887                 % (len(InterfaceResiduesChainsWords), Options["--interfaceResiduesChains"])
  888             )
  889         for ChainID in InterfaceResiduesChainsWords:
  890             ChainIDWords = ChainID.split("+")
  891             InterfaceResiduesChainsList.append(ChainIDWords)
  892     OptionsInfo["InterfaceResiduesChains"] = InterfaceResiduesChains
  893     OptionsInfo["InterfaceResiduesChainsList"] = InterfaceResiduesChainsList
  894 
  895     InterfaceResiduesCutoff = Options["--interfaceResiduesCutoff"]
  896     InterfaceResiduesMethod = OptionsInfo["InterfaceResiduesMethod"]
  897     if re.match("^auto$", InterfaceResiduesCutoff, re.I):
  898         if re.match("^BySASAChange$", InterfaceResiduesMethod, re.I):
  899             InterfaceResiduesCutoff = 1.0
  900         elif re.match("^ByHeavyAtomsDistance$", InterfaceResiduesMethod, re.I):
  901             InterfaceResiduesCutoff = 5.0
  902         elif re.match("^ByCAlphaAtomsDistance$", InterfaceResiduesMethod, re.I):
  903             InterfaceResiduesCutoff = 8.0
  904         else:
  905             MiscUtil.PrintError(
  906                 'The specified value, %s, for option "--interfaceResiduesMethod" is not supported.'
  907                 % (InterfaceResiduesMethod)
  908             )
  909     else:
  910         InterfaceResiduesCutoff = float(InterfaceResiduesCutoff)
  911     OptionsInfo["InterfaceResiduesCutoff"] = InterfaceResiduesCutoff
  912 
  913     OptionsInfo["Ligands"] = Options["--ligands"]
  914 
  915     OptionsInfo["PocketLigands"] = Options["--pocketLigands"]
  916     OptionsInfo["PocketDistanceCutoff"] = float(Options["--pocketDistanceCutoff"])
  917     OptionsInfo["PocketSolvents"] = Options["--pocketSolvents"]
  918     OptionsInfo["PocketInorganics"] = Options["--pocketInorganics"]
  919 
  920     OptionsInfo["PhiPsi"] = Options["--phiPsi"]
  921     OptionsInfo["PhiPsiMode"] = Options["--phiPsiMode"]
  922     OptionsInfo["PhiPsiPrecision"] = int(Options["--phiPsiPrecision"])
  923 
  924     OptionsInfo["Solvents"] = Options["--solvents"]
  925 
  926     OptionsInfo["SurfaceResidues"] = Options["--surfaceResidues"]
  927     OptionsInfo["SurfaceResiduesCutoff"] = float(Options["--surfaceResiduesCutoff"])
  928     OptionsInfo["SurfaceResiduesIDs"] = True if re.match("^Yes$", Options["--surfaceResiduesIDs"], re.I) else False
  929 
  930     # Always list header, chains, and ligands...
  931     OptionNames = ["Chains", "Header", "Ligands"]
  932     for Name in OptionNames:
  933         if Name in OptionsInfo:
  934             OptionsInfo[Name] = True
  935         else:
  936             MiscUtil.PrintError("Option name %s is not a valid name..." % Name)
  937 
  938 
  939 def RetrieveOptions():
  940     """Retrieve command line arguments and options."""
  941 
  942     # Get options...
  943     global Options
  944     Options = docopt(_docoptUsage_)
  945 
  946     # Set current working directory to the specified directory...
  947     WorkingDir = Options["--workingdir"]
  948     if WorkingDir:
  949         os.chdir(WorkingDir)
  950 
  951     # Handle examples option...
  952     if "--examples" in Options and Options["--examples"]:
  953         MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_))
  954         sys.exit(0)
  955 
  956 
  957 def ValidateOptions():
  958     """Validate option values."""
  959 
  960     # Expand infile names..
  961     InfilesNames = MiscUtil.ExpandFileNames(Options["--infiles"], ",")
  962 
  963     # Validate file extensions...
  964     for Infile in InfilesNames:
  965         MiscUtil.ValidateOptionFilePath("-i, --infiles", Infile)
  966         MiscUtil.ValidateOptionFileExt("-i, --infiles", Infile, "pdb cif")
  967     Options["--infilesNames"] = InfilesNames
  968 
  969     if not re.match("^auto$", Options["--interfaceResiduesCutoff"], re.I):
  970         MiscUtil.ValidateOptionFloatValue("--interfaceResiduesCutoff", Options["--interfaceResiduesCutoff"], {">": 0.0})
  971     MiscUtil.ValidateOptionTextValue(
  972         "--interfaceResiduesMethod",
  973         Options["--interfaceResiduesMethod"],
  974         "BySASAChange ByHeavyAtomsDistance ByCAlphaAtomsDistance",
  975     )
  976 
  977     MiscUtil.ValidateOptionFloatValue("--pocketDistanceCutoff", Options["--pocketDistanceCutoff"], {">": 0.0})
  978     MiscUtil.ValidateOptionTextValue("--phiPsiMode", Options["--phiPsiMode"], "All  Categories")
  979     MiscUtil.ValidateOptionIntegerValue("--phiPsiPrecision", Options["--phiPsiPrecision"], {">": 0})
  980     MiscUtil.ValidateOptionFloatValue("--surfaceResiduesCutoff", Options["--surfaceResiduesCutoff"], {">": 0.0})
  981     MiscUtil.ValidateOptionTextValue("--surfaceResiduesIDs", Options["--surfaceResiduesIDs"], "Yes No")
  982 
  983 
  984 # Setup a usage string for docopt...
  985 _docoptUsage_ = """
  986 PyMOLInfoMacromolecules.py - List information about macromolecules
  987 
  988 Usage:
  989     PyMOLInfoMacromolecules.py [--all] [--boundingBox] [--centroid] [--chains]
  990                                [--countResidues] [--header] [--inorganics] [--interfaceResidues]
  991                                [--interfaceResiduesChains <ChainID1,ChainD2,...>] [--interfaceResiduesMethod <text>]
  992                                [--interfaceResiduesCutoff <number>] [--ligands] [--pocketLigands]
  993                                [--pocketDistanceCutoff  <number>] [--pocketSolvents] [--pocketInorganics]
  994                                [--phiPsi] [--phiPsiMode <All or Categories>] [--phiPsiPrecision <number>]
  995                                [--surfaceResidues] [--surfaceResiduesCutoff <number>] [--surfaceResiduesIDs <yes or no>]
  996                                [--solvents] [-w <dir>] -i <infile1,infile2,infile3...>
  997     PyMOLInfoMacromolecules.py -h | --help | -e | --examples
  998 
  999 Description:
 1000     List information regarding  ID, classification, experimental technique, chains,
 1001     solvents, inorganics, ligands, and ligand binding pockets in macromolecules
 1002     present including proteins and nucleic acids.
 1003 
 1004     The supported input  file format are: PDB (.pdb), mmCIF (.cif)
 1005 
 1006 Options:
 1007     -a, --all
 1008         All available information.
 1009     -b, --boundingBox
 1010         Min and max coordinates for bounding box along with its size.
 1011     -c, --chains
 1012         Number of chains and their IDs. This is also default behavior.
 1013      --centroid
 1014         Centroid of atomic coordinates.  It corresponds to the mean of all 3D
 1015         coordinates in input file.
 1016      --countResidues
 1017         Number of residues across chains. The chain residues are identified
 1018         using polymer selection operator available in PyMOL. In addition,
 1019         the non-standard amino acid residues are listed.
 1020     -e, --examples
 1021         Print examples.
 1022     -h, --help
 1023         Print this help message.
 1024      --header
 1025         Header information including experimental technique information
 1026         along with any available resolution. This is also default behavior.
 1027     -i, --infiles <infile1,infile2,infile3...>
 1028         A comma delimited list of input files. The wildcards are also allowed
 1029         in file names.
 1030     --inorganics
 1031         Inorganic residues across chains. The inorganic residues are identified
 1032         using inorganic selection operator available in PyMOL.
 1033     --interfaceResidues
 1034         Interface residues between specified pairs of chains.
 1035     --interfaceResiduesChains <ChainID1,Chain1D2,...>  [default: Auto]
 1036         Pairwise comma delimited list of chain IDs for the identification of
 1037         interface residues. Each chain ID may contain mutiple chain IDs
 1038         delimited by a plus sign. For example: A+B,C+D chain pair specifies
 1039         interface between chain complexes A+B and C+D.
 1040         
 1041         The interface residues are identified between first two chains in
 1042         input files by default.
 1043     --interfaceResiduesMethod <text>  [default: BySASAChange]
 1044         Methodology for the identification of interface residues between a pair
 1045         of chains in an input file. The interface residues may be identified by
 1046         change in solvent accessible surface area (SASA) for a residue between
 1047         a chain and chains complex, distance between heavy atoms
 1048         in two chains, or distance between CAlpha atoms. Possible values:
 1049         BySASAChange, ByHeavyAtomsDistance, or ByCAlphaAtomsDistance. 
 1050     --interfaceResiduesCutoff <number>  [default: auto]
 1051         Cutoff value used by different methodologies during identification of
 1052         interface residues between a pair of chains. The default values are
 1053         shown below:
 1054         
 1055             BySASAChange: 1.0; Units: Angstrom**2 [ Ref 141 ]
 1056             ByHeavyAtomsDistance: 5.0; Units: Angstrom [ Ref 142 ]
 1057             ByCAlphaAtomsDistance: 8.0; Units: Angstrom [ Ref 143 ]
 1058         
 1059     -l, --ligands
 1060         Ligands across chains. This is also default behavior. The ligands
 1061         residues are identified using organic selection operator available
 1062         in PyMOL.
 1063     -p, --pocketLigands
 1064         Chain residues in ligand pockets.
 1065     --pocketDistanceCutoff <number>  [default: 5.0]
 1066         Distance in Angstroms for identifying pocket residues around ligands.
 1067     --pocketSolvents
 1068         Solvent residues in ligand pockets. The solvent residues are identified
 1069         using solvent selection operator available in PyMOL.
 1070     --pocketInorganics
 1071         Inorganic residues in ligand pockets. The inorganic residues are identified
 1072         using Inorganic selection operator available in PyMOL.
 1073     --phiPsi
 1074         Phi and psi torsion angles across chains in macromolecules containing
 1075         amino acids.
 1076     --phiPsiMode <All or Categories>  [default: Categories]
 1077         List all phi and psi torsion angles for residues as a single group or split
 1078         them into the following categories corresponding to four types of
 1079         Ramachandran plots:
 1080         
 1081             General: All residues except glycine, proline, or pre-proline
 1082             Glycine: Only glycine residues
 1083             Proline: Only proline residues
 1084             Pre-Proline: Only residues before proline not including glycine
 1085                 or proline
 1086         
 1087     --phiPsiPrecision <number>  [default: 2]
 1088         Precision for listing phi and psi torsion angles.
 1089     -s, --solvents
 1090         Solvent residues across chains. The solvent residues are identified
 1091         using solvent selection operator available in PyMOL.
 1092     --surfaceResidues
 1093         Surface and buried residues in chains.
 1094     --surfaceResiduesCutoff <number>  [default: 2.5]
 1095         Solvenet Accessible Surface Area (SASA) cutoff value in Angstroms**2
 1096         for surface and buried resiudes in chains. The residues with SASA less than
 1097         the cutoff value correspond to burried residues.
 1098     --surfaceResiduesIDs <yes or no>  [default: No]
 1099         List residue IDs for surface and buried residues during listing of the
 1100         distribution of these residues for '--surfaceResidues' option.
 1101     -w, --workingdir <dir>
 1102         Location of working directory which defaults to the current directory.
 1103 
 1104 Examples:
 1105     To list header, chains, and ligand information for macromolecules in input
 1106     file, type:
 1107 
 1108         % PyMOLInfoMacromolecules.py  -i Sample3.pdb
 1109 
 1110     To list all available information for macromolecules in input files, type:
 1111 
 1112         % PyMOLInfoMacromolecules.py  -a  -i "Sample3.pdb,Sample4.pdb"
 1113 
 1114     To list pockets residues information along with other default information
 1115     for marcomolecules in input file, type:
 1116 
 1117         % PyMOLInfoMacromolecules.py  -p --pocketDistanceCutoff 4.5 
 1118         --pocketSolvents  --pocketInorganics -i Sample3.pdb
 1119 
 1120     To list chain residues information along with other default information
 1121     for marcomolecules in input file, type:
 1122 
 1123         % PyMOLInfoMacromolecules.py  -c --countResidues --solvents
 1124         --inorganics -i "Sample3.pdb,Sample4.pdb" 
 1125 
 1126     To list interface residues between first two chains by SASA change for
 1127     marcomolecules in input file, type:
 1128 
 1129         % PyMOLInfoMacromolecules.py  --interfaceResidues
 1130         -i Sample3.pdb
 1131 
 1132     To list interface residues between chains E and I  by heay atoms
 1133     distance for marcomolecules in input file, type:
 1134 
 1135         % PyMOLInfoMacromolecules.py  --interfaceResidues
 1136         --interfaceResiduesChains E,I  --interfaceResiduesMethod
 1137         ByHeavyAtomsDistance --interfaceResiduesCutoff  5 -i Sample3.pdb
 1138 
 1139     To list interface residues between two sets of chains by SASA change for
 1140     marcomolecules in input file, type:
 1141 
 1142         % PyMOLInfoMacromolecules.py  --interfaceResidues
 1143         --interfaceResiduesChains "A+B,C+D" -i Sample8.pdb 
 1144 
 1145 Author:
 1146     Manish Sud(msud@san.rr.com)
 1147 
 1148 See also:
 1149     DownloadPDBFiles.pl, PyMOLSplitChainsAndLigands.py,
 1150     PyMOLVisualizeMacromolecules.py
 1151 
 1152 Copyright:
 1153     Copyright (C) 2026 Manish Sud. All rights reserved.
 1154 
 1155     The functionality available in this script is implemented using PyMOL, a
 1156     molecular visualization system on an open source foundation originally
 1157     developed by Warren DeLano.
 1158 
 1159     This file is part of MayaChemTools.
 1160 
 1161     MayaChemTools is free software; you can redistribute it and/or modify it under
 1162     the terms of the GNU Lesser General Public License as published by the Free
 1163     Software Foundation; either version 3 of the License, or (at your option) any
 1164     later version.
 1165 
 1166 """
 1167 
 1168 if __name__ == "__main__":
 1169     main()