MayaChemTools

    1 #!/bin/env python
    2 #
    3 # File: PyMOLVisualizeInterfaces.py
    4 # Author: Manish Sud <msud@san.rr.com>
    5 #
    6 # Copyright (C) 2025 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     GenerateMacromolecularInterfacesVisualization()
   84 
   85     MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName)
   86     MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime))
   87 
   88 
   89 def GenerateMacromolecularInterfacesVisualization():
   90     """Generate visualization for macromolecular interfaces."""
   91 
   92     Outfile = OptionsInfo["PMLOutfile"]
   93     OutFH = open(Outfile, "w")
   94     if OutFH is None:
   95         MiscUtil.PrintError("Failed to open output fie %s " % Outfile)
   96 
   97     MiscUtil.PrintInfo("\nGenerating file %s..." % Outfile)
   98 
   99     # Setup header...
  100     WritePMLHeader(OutFH, ScriptName)
  101     WritePyMOLParameters(OutFH)
  102 
  103     WriteComplexesAndChainsViews(OutFH)
  104     WriteInterfaceViews(OutFH)
  105 
  106     OutFH.close()
  107 
  108     # Generate PSE file as needed...
  109     if OptionsInfo["PSEOut"]:
  110         GeneratePyMOLSessionFile()
  111 
  112 
  113 def WriteComplexesAndChainsViews(OutFH):
  114     """Write out PML for viewing complexes and chains in input files."""
  115 
  116     # Setup views for input file(s)...
  117     for FileIndex in range(0, len(OptionsInfo["InfilesInfo"]["InfilesNames"])):
  118         # Setup PyMOL object names...
  119         PyMOLObjectNamesInfo = OptionsInfo["InfilesInfo"]["PyMOLObjectNamesInfo"][FileIndex]
  120 
  121         # Setup complex view...
  122         WriteComplexView(OutFH, FileIndex, PyMOLObjectNamesInfo)
  123 
  124         # Setup chain views...
  125         SpecifiedChainsAndLigandsInfo = OptionsInfo["InfilesInfo"]["SpecifiedChainsAndLigandsInfo"][FileIndex]
  126 
  127         for ChainID in SpecifiedChainsAndLigandsInfo["ChainIDs"]:
  128             WriteChainView(OutFH, FileIndex, PyMOLObjectNamesInfo, ChainID)
  129 
  130             # Setup ligand views...
  131             for LigandID in SpecifiedChainsAndLigandsInfo["LigandIDs"][ChainID]:
  132                 WriteChainLigandView(OutFH, FileIndex, PyMOLObjectNamesInfo, ChainID, LigandID)
  133 
  134                 # Set up ligand level group...
  135                 Enable, Action = [True, "close"]
  136                 GenerateAndWritePMLForGroup(
  137                     OutFH,
  138                     PyMOLObjectNamesInfo["Ligands"][ChainID][LigandID]["ChainLigandGroup"],
  139                     PyMOLObjectNamesInfo["Ligands"][ChainID][LigandID]["ChainLigandGroupMembers"],
  140                     Enable,
  141                     Action,
  142                 )
  143 
  144             # Setup Chain level group...
  145             Enable, Action = [True, "open"]
  146             GenerateAndWritePMLForGroup(
  147                 OutFH,
  148                 PyMOLObjectNamesInfo["Chains"][ChainID]["ChainGroup"],
  149                 PyMOLObjectNamesInfo["Chains"][ChainID]["ChainGroupMembers"],
  150                 Enable,
  151                 Action,
  152             )
  153 
  154         # Set up complex level group...
  155         Enable, Action = [True, "close"]
  156         GenerateAndWritePMLForGroup(
  157             OutFH, PyMOLObjectNamesInfo["PDBGroup"], PyMOLObjectNamesInfo["PDBGroupMembers"], Enable, Action
  158         )
  159 
  160         # Delete empty PyMOL objects...
  161         DeleteEmptyPyMOLObjects(OutFH, FileIndex, PyMOLObjectNamesInfo)
  162 
  163 
  164 def WriteInterfaceViews(OutFH):
  165     """Write out PML for viewing macromolecular interfaces among specified chains."""
  166 
  167     InterfaceChainsAndResiduesInfo = OptionsInfo["InfilesInfo"]["InterfaceChainsAndResiduesInfo"]
  168     if InterfaceChainsAndResiduesInfo is None:
  169         return
  170 
  171     PyMOLInterfaceObjectNamesInfo = OptionsInfo["InfilesInfo"]["PyMOLInterfaceObjectNamesInfo"]
  172     InterfaceChainPairsAndResiduesInfo = OptionsInfo["InfilesInfo"]["InterfaceChainPairsAndResiduesInfo"]
  173 
  174     FirstInterfaceID = True
  175     for InterfaceID in InterfaceChainsAndResiduesInfo["InterfaceIDs"]:
  176         WriteInterfacePolarContactsView(
  177             OutFH, InterfaceID, InterfaceChainPairsAndResiduesInfo, PyMOLInterfaceObjectNamesInfo
  178         )
  179         WriteInterfaceHydrophobicContactsView(
  180             OutFH, InterfaceID, InterfaceChainPairsAndResiduesInfo, PyMOLInterfaceObjectNamesInfo
  181         )
  182 
  183         for InterfaceChainID in InterfaceChainsAndResiduesInfo["InterfaceChainIDs"][InterfaceID]:
  184             ChainID = InterfaceChainsAndResiduesInfo["ChainIDs"][InterfaceID][InterfaceChainID]
  185             ResNums = InterfaceChainsAndResiduesInfo["ChainIDsResNums"][InterfaceID][InterfaceChainID]
  186             ComplexName = InterfaceChainsAndResiduesInfo["ChainIDsComplexNames"][InterfaceID][InterfaceChainID]
  187             FileIndex = InterfaceChainsAndResiduesInfo["ChainIDsInfileIndices"][InterfaceID][InterfaceChainID]
  188 
  189             WriteInterfaceChainView(
  190                 OutFH,
  191                 FileIndex,
  192                 InterfaceID,
  193                 InterfaceChainID,
  194                 ChainID,
  195                 ResNums,
  196                 ComplexName,
  197                 PyMOLInterfaceObjectNamesInfo,
  198             )
  199 
  200             # Setup interface Chain level group...
  201             Enable, Action = [True, "open"]
  202             GenerateAndWritePMLForGroup(
  203                 OutFH,
  204                 PyMOLInterfaceObjectNamesInfo["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainGroup"],
  205                 PyMOLInterfaceObjectNamesInfo["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainGroupMembers"],
  206                 Enable,
  207                 Action,
  208             )
  209 
  210         if FirstInterfaceID:
  211             FirstInterfaceID = False
  212             Enable, Action = [True, "open"]
  213         else:
  214             Enable, Action = [True, "close"]
  215 
  216         GenerateAndWritePMLForGroup(
  217             OutFH,
  218             PyMOLInterfaceObjectNamesInfo["InterfaceIDs"][InterfaceID]["InterfaceIDGroup"],
  219             PyMOLInterfaceObjectNamesInfo["InterfaceIDs"][InterfaceID]["InterfaceIDGroupMembers"],
  220             Enable,
  221             Action,
  222         )
  223 
  224     # Setup interfaces level group...
  225     Enable, Action = [True, "open"]
  226     GenerateAndWritePMLForGroup(
  227         OutFH,
  228         PyMOLInterfaceObjectNamesInfo["InterfacesGroup"],
  229         PyMOLInterfaceObjectNamesInfo["InterfacesGroupMembers"],
  230         Enable,
  231         Action,
  232     )
  233 
  234     DeleteEmptyPyMOLInterfaceObjects(OutFH)
  235 
  236     # Setup orientation...
  237     OutFH.write("""\ncmd.orient("visible", animate = -1)\n""")
  238 
  239 
  240 def WritePMLHeader(OutFH, ScriptName):
  241     """Write out PML setting up complex view."""
  242 
  243     HeaderInfo = PyMOLUtil.SetupPMLHeaderInfo(ScriptName)
  244     OutFH.write("%s\n" % HeaderInfo)
  245 
  246 
  247 def WritePyMOLParameters(OutFH):
  248     """Write out PyMOL global parameters."""
  249 
  250     PMLCmds = []
  251     PMLCmds.append("""cmd.set("transparency", %.2f, "", 0)""" % (OptionsInfo["SurfaceTransparency"]))
  252     PMLCmds.append("""cmd.set("label_font_id", %s)""" % (OptionsInfo["LabelFontID"]))
  253     PML = "\n".join(PMLCmds)
  254 
  255     OutFH.write("""\n""\n"Setting up PyMOL gobal parameters..."\n""\n""")
  256     OutFH.write("%s\n" % PML)
  257 
  258 
  259 def WriteComplexView(OutFH, FileIndex, PyMOLObjectNames):
  260     """Write out PML for viewing polymer complex."""
  261 
  262     # Setup complex...
  263     Infile = OptionsInfo["InfilesInfo"]["InfilesNames"][FileIndex]
  264     PML = PyMOLUtil.SetupPMLForPolymerComplexView(PyMOLObjectNames["Complex"], Infile, True)
  265     OutFH.write("""\n""\n"Loading %s and setting up view for complex..."\n""\n""" % Infile)
  266     OutFH.write("%s\n" % PML)
  267 
  268     if OptionsInfo["SurfaceComplex"]:
  269         # Setup hydrophobic surface...
  270         PML = PyMOLUtil.SetupPMLForHydrophobicSurfaceView(
  271             PyMOLObjectNames["ComplexHydrophobicSurface"],
  272             PyMOLObjectNames["Complex"],
  273             ColorPalette=OptionsInfo["SurfaceColorPalette"],
  274             Enable=False,
  275         )
  276         OutFH.write("\n%s\n" % PML)
  277 
  278     # Setup complex group...
  279     GenerateAndWritePMLForGroup(
  280         OutFH, PyMOLObjectNames["ComplexGroup"], PyMOLObjectNames["ComplexGroupMembers"], False, "close"
  281     )
  282 
  283 
  284 def WriteChainView(OutFH, FileIndex, PyMOLObjectNames, ChainID):
  285     """Write out PML for viewing chain."""
  286 
  287     OutFH.write("""\n""\n"Setting up views for chain %s..."\n""\n""" % ChainID)
  288 
  289     ChainComplexName = PyMOLObjectNames["Chains"][ChainID]["ChainComplex"]
  290 
  291     # Setup chain complex group view...
  292     WriteChainComplexViews(OutFH, FileIndex, PyMOLObjectNames, ChainID)
  293 
  294     # Setup chain view...
  295     WriteChainAloneViews(OutFH, FileIndex, PyMOLObjectNames, ChainID)
  296 
  297     # Setup chain solvent view...
  298     PML = PyMOLUtil.SetupPMLForSolventView(PyMOLObjectNames["Chains"][ChainID]["Solvent"], ChainComplexName, False)
  299     OutFH.write("\n%s\n" % PML)
  300 
  301     # Setup chain inorganic view...
  302     PML = PyMOLUtil.SetupPMLForInorganicView(PyMOLObjectNames["Chains"][ChainID]["Inorganic"], ChainComplexName, False)
  303     OutFH.write("\n%s\n" % PML)
  304 
  305 
  306 def WriteChainComplexViews(OutFH, FileIndex, PyMOLObjectNames, ChainID):
  307     """Write chain complex views."""
  308 
  309     # Setup chain complex...
  310     ChainComplexName = PyMOLObjectNames["Chains"][ChainID]["ChainComplex"]
  311     PML = PyMOLUtil.SetupPMLForPolymerChainComplexView(ChainComplexName, PyMOLObjectNames["Complex"], ChainID, True)
  312     OutFH.write("%s\n" % PML)
  313 
  314     if OptionsInfo["SurfaceChainComplex"]:
  315         # Setup hydrophobic surface...
  316         PML = PyMOLUtil.SetupPMLForHydrophobicSurfaceView(
  317             PyMOLObjectNames["Chains"][ChainID]["ChainComplexHydrophobicSurface"],
  318             ChainComplexName,
  319             ColorPalette=OptionsInfo["SurfaceColorPalette"],
  320             Enable=False,
  321         )
  322         OutFH.write("\n%s\n" % PML)
  323 
  324     # Setup chain complex group...
  325     GenerateAndWritePMLForGroup(
  326         OutFH,
  327         PyMOLObjectNames["Chains"][ChainID]["ChainComplexGroup"],
  328         PyMOLObjectNames["Chains"][ChainID]["ChainComplexGroupMembers"],
  329         False,
  330         "close",
  331     )
  332 
  333 
  334 def WriteChainAloneViews(OutFH, FileIndex, PyMOLObjectNames, ChainID):
  335     """Write individual chain views."""
  336 
  337     ChainComplexName = PyMOLObjectNames["Chains"][ChainID]["ChainComplex"]
  338 
  339     # Setup chain view...
  340     ChainName = PyMOLObjectNames["Chains"][ChainID]["ChainAlone"]
  341     PML = PyMOLUtil.SetupPMLForPolymerChainView(ChainName, ChainComplexName, True)
  342     OutFH.write("\n%s\n" % PML)
  343 
  344     # Setup a non-interface chain view...
  345     NonInterfaceChainName = PyMOLObjectNames["Chains"][ChainID]["ChainAloneNonInterface"]
  346 
  347     InterfaceResNums = OptionsInfo["InfilesInfo"]["SpecifiedChainsAndLigandsInfo"][FileIndex]["InterfaceResNums"][
  348         ChainID
  349     ]
  350     InterfaceResNumsSelection = "+".join(InterfaceResNums)
  351 
  352     Selection = "%s and chain %s and polymer and (not (resi %s))" % (
  353         ChainComplexName,
  354         ChainID,
  355         InterfaceResNumsSelection,
  356     )
  357     PML = PyMOLUtil.SetupPMLForSelectionDisplayView(NonInterfaceChainName, Selection, "cartoon", Enable=False)
  358     OutFH.write("\n%s\n" % PML)
  359 
  360     if GetChainAloneContainsSurfacesStatus(FileIndex, ChainID):
  361         # Setup a generic colored surface...
  362         PML = PyMOLUtil.SetupPMLForSurfaceView(
  363             PyMOLObjectNames["Chains"][ChainID]["ChainAloneSurface"],
  364             NonInterfaceChainName,
  365             Enable=False,
  366             Color=OptionsInfo["SurfaceNonInterfaceColor"],
  367         )
  368         OutFH.write("\n%s\n" % PML)
  369 
  370         if GetChainAloneSurfaceChainStatus(FileIndex, ChainID):
  371             # Setup surface colored by hydrophobicity...
  372             PML = PyMOLUtil.SetupPMLForHydrophobicSurfaceView(
  373                 PyMOLObjectNames["Chains"][ChainID]["ChainAloneHydrophobicSurface"],
  374                 NonInterfaceChainName,
  375                 ColorPalette=OptionsInfo["SurfaceColorPalette"],
  376                 Enable=False,
  377             )
  378             OutFH.write("\n%s\n" % PML)
  379 
  380             # Setup surface colored by hyrdophobicity and charge...
  381             PML = PyMOLUtil.SetupPMLForHydrophobicAndChargeSurfaceView(
  382                 PyMOLObjectNames["Chains"][ChainID]["ChainAloneHydrophobicChargeSurface"],
  383                 NonInterfaceChainName,
  384                 OptionsInfo["AtomTypesColorNames"]["HydrophobicAtomsColor"],
  385                 OptionsInfo["AtomTypesColorNames"]["NegativelyChargedAtomsColor"],
  386                 OptionsInfo["AtomTypesColorNames"]["PositivelyChargedAtomsColor"],
  387                 OptionsInfo["AtomTypesColorNames"]["OtherAtomsColor"],
  388                 Enable=False,
  389                 DisplayAs=None,
  390             )
  391             OutFH.write("\n%s\n" % PML)
  392 
  393         if GetChainAloneSurfaceChainElectrostaticsStatus(FileIndex, ChainID):
  394             # Setup electrostatics surface...
  395             SelectionObjectName = NonInterfaceChainName
  396             ElectrostaticsGroupName = PyMOLObjectNames["Chains"][ChainID]["ChainAloneElectrostaticsGroup"]
  397             ElectrostaticsGroupMembers = PyMOLObjectNames["Chains"][ChainID]["ChainAloneElectrostaticsGroupMembers"]
  398             WriteSurfaceElectrostaticsView(
  399                 "Chain", OutFH, SelectionObjectName, ElectrostaticsGroupName, ElectrostaticsGroupMembers
  400             )
  401 
  402         # Setup surface group...
  403         GenerateAndWritePMLForGroup(
  404             OutFH,
  405             PyMOLObjectNames["Chains"][ChainID]["ChainAloneSurfaceGroup"],
  406             PyMOLObjectNames["Chains"][ChainID]["ChainAloneSurfaceGroupMembers"],
  407             True,
  408             "open",
  409         )
  410 
  411     # Setup a non-interface group...
  412     GenerateAndWritePMLForGroup(
  413         OutFH,
  414         PyMOLObjectNames["Chains"][ChainID]["ChainAloneNonInterfaceGroup"],
  415         PyMOLObjectNames["Chains"][ChainID]["ChainAloneNonInterfaceGroupMembers"],
  416         True,
  417         "open",
  418     )
  419 
  420     # Setup chain group...
  421     GenerateAndWritePMLForGroup(
  422         OutFH,
  423         PyMOLObjectNames["Chains"][ChainID]["ChainAloneGroup"],
  424         PyMOLObjectNames["Chains"][ChainID]["ChainAloneGroupMembers"],
  425         True,
  426         "close",
  427     )
  428 
  429 
  430 def WriteChainLigandView(OutFH, FileIndex, PyMOLObjectNames, ChainID, LigandID):
  431     """Write out PML for viewing ligand in a chain."""
  432 
  433     GroupID = "Ligand"
  434     ComplexName = PyMOLObjectNames["Chains"][ChainID]["ChainComplex"]
  435 
  436     # Setup main object...
  437     GroupTypeObjectID = "%s" % (GroupID)
  438     GroupTypeObjectName = PyMOLObjectNames["Ligands"][ChainID][LigandID][GroupTypeObjectID]
  439 
  440     OutFH.write("""\n""\n"Setting up views for ligand %s in chain %s..."\n""\n""" % (LigandID, ChainID))
  441     PML = PyMOLUtil.SetupPMLForLigandView(
  442         GroupTypeObjectName, ComplexName, LigandID, Enable=True, IgnoreHydrogens=OptionsInfo["IgnoreHydrogens"]
  443     )
  444     OutFH.write("%s\n" % PML)
  445 
  446     # Setup ball and stick view...
  447     BallAndStickNameID = "%sBallAndStick" % (GroupID)
  448     BallAndStickName = PyMOLObjectNames["Ligands"][ChainID][LigandID][BallAndStickNameID]
  449     PML = PyMOLUtil.SetupPMLForBallAndStickView(BallAndStickName, GroupTypeObjectName, Enable=False)
  450     OutFH.write("\n%s\n" % PML)
  451 
  452     # Setup group....
  453     GroupNameID = "%sGroup" % (GroupID)
  454     GroupMembersID = "%sGroupMembers" % (GroupID)
  455     GroupName = PyMOLObjectNames["Ligands"][ChainID][LigandID][GroupNameID]
  456     GroupMembers = PyMOLObjectNames["Ligands"][ChainID][LigandID][GroupMembersID]
  457 
  458     Action = "open"
  459     Enable = True
  460     GenerateAndWritePMLForGroup(OutFH, GroupName, GroupMembers, Enable, Action)
  461 
  462 
  463 def WriteInterfacePolarContactsView(OutFH, InterfaceID, InterfaceChainPairsAndResiduesInfo, PyMOLObjectNames):
  464     """Write out PML for viewing polar contacts between interface residues."""
  465 
  466     if not OptionsInfo["InterfacePolarContacts"]:
  467         return
  468 
  469     WriteInterfaceContactsView(
  470         "InterfacePolarConatcts", OutFH, InterfaceID, InterfaceChainPairsAndResiduesInfo, PyMOLObjectNames
  471     )
  472 
  473 
  474 def WriteInterfaceHydrophobicContactsView(OutFH, InterfaceID, InterfaceChainPairsAndResiduesInfo, PyMOLObjectNames):
  475     """Write out PML for viewing hydrophobic contacts between interface residues."""
  476 
  477     if not OptionsInfo["InterfaceHydrophobicContacts"]:
  478         return
  479 
  480     WriteInterfaceContactsView(
  481         "InterfaceHydrophobicContacts", OutFH, InterfaceID, InterfaceChainPairsAndResiduesInfo, PyMOLObjectNames
  482     )
  483 
  484 
  485 def WriteInterfaceContactsView(Mode, OutFH, InterfaceID, InterfaceChainPairsAndResiduesInfo, PyMOLObjectNames):
  486     """Write out PML for viewing polar or hydrophobic contacts between interface residues."""
  487 
  488     InterfacePolarContacts = True if re.match("^InterfacePolarConatcts$", Mode, re.I) else False
  489 
  490     ChainIDs1, ChainIDs2 = InterfaceChainPairsAndResiduesInfo["ChainIDsPairs"][InterfaceID]
  491     ResNums1, ResNums2 = InterfaceChainPairsAndResiduesInfo["ChainIDsResNumsPairs"][InterfaceID]
  492 
  493     FileIndex1, FileIndex2 = InterfaceChainPairsAndResiduesInfo["InfileIndicesPairs"][InterfaceID]
  494     ComplexName1 = OptionsInfo["InfilesInfo"]["PyMOLObjectNamesInfo"][FileIndex1]["Complex"]
  495     ComplexName2 = OptionsInfo["InfilesInfo"]["PyMOLObjectNamesInfo"][FileIndex2]["Complex"]
  496 
  497     Selection1 = SetupSelectionForInterfaceContactsView(ComplexName1, ChainIDs1, ResNums1)
  498     Selection2 = SetupSelectionForInterfaceContactsView(ComplexName2, ChainIDs2, ResNums2)
  499 
  500     if InterfacePolarContacts:
  501         ContactsName = PyMOLObjectNames["InterfaceIDs"][InterfaceID]["PolarContacts"]
  502         ContactsColor = OptionsInfo["InterfacePolarContactsColor"]
  503         ContactsCutoff = OptionsInfo["InterfaceContactsCutoff"]
  504 
  505         PML = PyMOLUtil.SetupPMLForPolarContactsView(
  506             ContactsName, Selection1, Selection2, Enable=False, Color=ContactsColor, Cutoff=ContactsCutoff
  507         )
  508     else:
  509         ContactsName = PyMOLObjectNames["InterfaceIDs"][InterfaceID]["HydrophobicContacts"]
  510         ContactsColor = OptionsInfo["InterfaceHydrophobicContactsColor"]
  511         ContactsCutoff = OptionsInfo["InterfaceContactsCutoff"]
  512 
  513         PML = PyMOLUtil.SetupPMLForHydrophobicContactsView(
  514             ContactsName, Selection1, Selection2, Enable=False, Color=ContactsColor, Cutoff=ContactsCutoff
  515         )
  516 
  517     OutFH.write("\n%s\n" % PML)
  518 
  519     OutFH.write("""cmd.set("label_color", "%s", "%s")\n""" % (ContactsColor, ContactsName))
  520 
  521 
  522 def SetupSelectionForInterfaceContactsView(ComplexName, ChainIDs, ResNums):
  523     """Setup a selection for generating polar or hyrophobic contacts for an interface."""
  524 
  525     ChainSelections = []
  526 
  527     for ChainID in ChainIDs:
  528         ChainResNumsSelection = "+".join(ResNums["ResNums"][ChainID])
  529         Selection = "(%s and chain %s and polymer and (resi %s))" % (ComplexName, ChainID, ChainResNumsSelection)
  530         ChainSelections.append(Selection)
  531 
  532     Selection = " or ".join(ChainSelections)
  533 
  534     return Selection
  535 
  536 
  537 def WriteInterfaceChainView(
  538     OutFH, FileIndex, InterfaceID, InterfaceChainID, ChainID, ResiduesNums, ComplexName, PyMOLObjectNames
  539 ):
  540     """Write out PML for viewing interface residues in a chain."""
  541 
  542     OutFH.write("""\n""\n"Setting up interface views for interface in %s..."\n""\n""" % InterfaceChainID)
  543 
  544     ResiduesNumsSelection = "+".join(ResiduesNums)
  545 
  546     # Setup a chain for interface residues...
  547     ChainName = PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["Chain"]
  548     Selection = "%s and chain %s and polymer and (resi %s)" % (ComplexName, ChainID, ResiduesNumsSelection)
  549 
  550     PML = PyMOLUtil.SetupPMLForSelectionDisplayView(ChainName, Selection, "lines", Enable=True)
  551     OutFH.write("\n%s\n" % PML)
  552 
  553     OutFH.write("""cmd.set("label_color", "%s", "%s")\n""" % (OptionsInfo["InterfaceLabelColor"], ChainName))
  554 
  555     WriteInterfaceChainResidueTypesView(OutFH, FileIndex, InterfaceID, InterfaceChainID, ChainID, PyMOLObjectNames)
  556 
  557     if GetInterfaceContainsSurfacesStatus(FileIndex, ChainID):
  558         # Setup generic colored surface...
  559         PML = PyMOLUtil.SetupPMLForSurfaceView(
  560             PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainSurface"],
  561             ChainName,
  562             Color=OptionsInfo["SurfaceInterfaceColor"],
  563             Enable=True,
  564             DisplayAs="lines",
  565         )
  566         OutFH.write("\n%s\n" % PML)
  567 
  568         if GetInterfaceSurfaceChainStatus(FileIndex, ChainID):
  569             # Setup surface colored by hydrophobicity...
  570             PML = PyMOLUtil.SetupPMLForHydrophobicSurfaceView(
  571                 PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainHydrophobicSurface"],
  572                 ChainName,
  573                 ColorPalette=OptionsInfo["SurfaceColorPalette"],
  574                 Enable=False,
  575                 DisplayAs="lines",
  576             )
  577             OutFH.write("\n%s\n" % PML)
  578 
  579             # Setup surface colored by hyrdophobicity and charge...
  580             PML = PyMOLUtil.SetupPMLForHydrophobicAndChargeSurfaceView(
  581                 PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainHydrophobicChargeSurface"],
  582                 ChainName,
  583                 OptionsInfo["AtomTypesColorNames"]["HydrophobicAtomsColor"],
  584                 OptionsInfo["AtomTypesColorNames"]["NegativelyChargedAtomsColor"],
  585                 OptionsInfo["AtomTypesColorNames"]["PositivelyChargedAtomsColor"],
  586                 OptionsInfo["AtomTypesColorNames"]["OtherAtomsColor"],
  587                 Enable=False,
  588                 DisplayAs="lines",
  589             )
  590             OutFH.write("\n%s\n" % PML)
  591 
  592         if GetInterfaceSurfaceChainElectrostaticsStatus(FileIndex, ChainID):
  593             # Setup electrostatics surface...
  594             SelectionObjectName = ChainName
  595             ElectrostaticsGroupName = PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID][
  596                 "ChainElectrostaticsGroup"
  597             ]
  598             ElectrostaticsGroupMembers = PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID][
  599                 "ChainElectrostaticsGroupMembers"
  600             ]
  601             WriteSurfaceElectrostaticsView(
  602                 "InterfaceResidues", OutFH, SelectionObjectName, ElectrostaticsGroupName, ElectrostaticsGroupMembers
  603             )
  604 
  605         # Setup surface group...
  606         GenerateAndWritePMLForGroup(
  607             OutFH,
  608             PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainSurfaceGroup"],
  609             PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainSurfaceGroupMembers"],
  610             True,
  611             "open",
  612         )
  613 
  614     # Setup chain group...
  615     GenerateAndWritePMLForGroup(
  616         OutFH,
  617         PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainGroup"],
  618         PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainGroupMembers"],
  619         True,
  620         "close",
  621     )
  622 
  623 
  624 def WriteInterfaceChainResidueTypesView(OutFH, FileIndex, InterfaceID, InterfaceChainID, ChainID, PyMOLObjectNames):
  625     """Write out PML for viewing interface residue types for a chain."""
  626 
  627     if not GetInterfaceResidueTypesStatus(FileIndex, ChainID):
  628         return
  629 
  630     ChainName = PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["Chain"]
  631 
  632     # Setup residue types objects...
  633     ResiduesGroupIDPrefix = "ChainResidues"
  634     for SubGroupType in ["Aromatic", "Hydrophobic", "Polar", "Positively_Charged", "Negatively_Charged", "Other"]:
  635         SubGroupID = re.sub("_", "", SubGroupType)
  636 
  637         ResiduesObjectID = "%s%sResidues" % (ResiduesGroupIDPrefix, SubGroupID)
  638         ResiduesObjectName = PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID][ResiduesObjectID]
  639 
  640         ResiduesSurfaceObjectID = "%s%sSurface" % (ResiduesGroupIDPrefix, SubGroupID)
  641         ResiduesSurfaceObjectName = PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID][
  642             ResiduesSurfaceObjectID
  643         ]
  644 
  645         ResiduesColor = OptionsInfo["ResidueTypesParams"][SubGroupType]["Color"]
  646         ResiduesNames = OptionsInfo["ResidueTypesParams"][SubGroupType]["Residues"]
  647 
  648         NegateResidueNames = True if re.match("^Other$", SubGroupType, re.I) else False
  649         WriteResidueTypesResiduesAndSurfaceView(
  650             OutFH,
  651             ChainName,
  652             ResiduesObjectName,
  653             ResiduesSurfaceObjectName,
  654             ResiduesColor,
  655             ResiduesNames,
  656             NegateResidueNames,
  657         )
  658 
  659         # Setup residue type sub groups...
  660         ResiduesSubGroupID = "%s%sGroup" % (ResiduesGroupIDPrefix, SubGroupID)
  661         ResiduesSubGroupMembersID = "%s%sGroupMembers" % (ResiduesGroupIDPrefix, SubGroupID)
  662 
  663         GenerateAndWritePMLForGroup(
  664             OutFH,
  665             PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID][ResiduesSubGroupID],
  666             PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID][ResiduesSubGroupMembersID],
  667             True,
  668             "close",
  669         )
  670 
  671     # Setup residue types group...
  672     GenerateAndWritePMLForGroup(
  673         OutFH,
  674         PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainResiduesGroup"],
  675         PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainResiduesGroupMembers"],
  676         False,
  677         "close",
  678     )
  679 
  680 
  681 def WriteResidueTypesResiduesAndSurfaceView(
  682     OutFH, SelectionObjectName, Name, SurfaceName, ResiduesColor, ResiduesNames, NegateResidueNames
  683 ):
  684     """Write residue types residues and surface view."""
  685 
  686     ResidueNamesSelection = "+".join(ResiduesNames)
  687     if NegateResidueNames:
  688         Selection = "%s and (not resn %s)" % (SelectionObjectName, ResidueNamesSelection)
  689     else:
  690         Selection = "%s and (resn %s)" % (SelectionObjectName, ResidueNamesSelection)
  691 
  692     # Setup residues...
  693     PML = PyMOLUtil.SetupPMLForSelectionDisplayView(Name, Selection, "lines", ResiduesColor, True)
  694     OutFH.write("\n%s\n" % PML)
  695 
  696     # Setup surface...
  697     PML = PyMOLUtil.SetupPMLForSelectionDisplayView(SurfaceName, Selection, "surface", ResiduesColor, True)
  698     OutFH.write("\n%s\n" % PML)
  699 
  700 
  701 def WriteSurfaceElectrostaticsView(
  702     Mode, OutFH, SelectionObjectName, ElectrostaticsGroupName, ElectrostaticsGroupMembers
  703 ):
  704     """Write out PML for viewing surface electrostatics."""
  705 
  706     if len(ElectrostaticsGroupMembers) == 5:
  707         Name, ContactPotentialName, MapName, LegendName, VolumeName = ElectrostaticsGroupMembers
  708     else:
  709         Name, ContactPotentialName, MapName, LegendName = ElectrostaticsGroupMembers
  710         VolumeName = None
  711 
  712     PMLCmds = []
  713 
  714     # Setup chain...
  715     PMLCmds.append("""cmd.create("%s", "(%s)")""" % (Name, SelectionObjectName))
  716 
  717     # Setup vacuum electrostatics surface along with associated objects...
  718     PMLCmds.append("""util.protein_vacuum_esp("%s", mode=2, quiet=0, _self=cmd)""" % (Name))
  719 
  720     PMLCmds.append("""cmd.set_name("%s_e_chg", "%s")""" % (Name, ContactPotentialName))
  721     if re.match("^Chain$", Mode, re.I):
  722         DisplayStyle = "cartoon"
  723     else:
  724         DisplayStyle = "lines"
  725     PMLCmds.append("""cmd.show("%s", "(%s)")""" % (DisplayStyle, ContactPotentialName))
  726     PMLCmds.append(PyMOLUtil.SetupPMLForEnableDisable(ContactPotentialName, Enable=True))
  727 
  728     PMLCmds.append("""cmd.set_name("%s_e_map", "%s")""" % (Name, MapName))
  729     PMLCmds.append(PyMOLUtil.SetupPMLForEnableDisable(MapName, Enable=False))
  730 
  731     PMLCmds.append("""cmd.set_name("%s_e_pot", "%s")""" % (Name, LegendName))
  732     PMLCmds.append(PyMOLUtil.SetupPMLForEnableDisable(LegendName, Enable=False))
  733 
  734     if VolumeName is not None:
  735         PMLCmds.append("""cmd.volume("%s", "%s", "%s", "(%s)")""" % (VolumeName, MapName, "esp", Name))
  736         PMLCmds.append(PyMOLUtil.SetupPMLForEnableDisable(VolumeName, Enable=False))
  737 
  738     # Delete name and take it out from the group membership. It is
  739     # is already part of ContactPotential object.
  740     PMLCmds.append("""cmd.delete("%s")""" % (Name))
  741     ElectrostaticsGroupMembers.pop(0)
  742 
  743     PML = "\n".join(PMLCmds)
  744 
  745     OutFH.write("\n%s\n" % PML)
  746 
  747     # Setup group...
  748     GenerateAndWritePMLForGroup(OutFH, ElectrostaticsGroupName, ElectrostaticsGroupMembers, False, "close")
  749 
  750 
  751 def GenerateAndWritePMLForGroup(OutFH, GroupName, GroupMembers, Enable=False, Action="close"):
  752     """Generate and write PML for group."""
  753 
  754     PML = PyMOLUtil.SetupPMLForGroup(GroupName, GroupMembers, Enable, Action)
  755     OutFH.write("""\n""\n"Setting up group %s..."\n""\n""" % GroupName)
  756     OutFH.write("%s\n" % PML)
  757 
  758 
  759 def GeneratePyMOLSessionFile():
  760     """Generate PME file from PML file."""
  761 
  762     PSEOutfile = OptionsInfo["PSEOutfile"]
  763     PMLOutfile = OptionsInfo["PMLOutfile"]
  764 
  765     MiscUtil.PrintInfo("\nGenerating file %s..." % PSEOutfile)
  766 
  767     PyMOLUtil.ConvertPMLFileToPSEFile(PMLOutfile, PSEOutfile)
  768 
  769     if not os.path.exists(PSEOutfile):
  770         MiscUtil.PrintWarning("Failed to generate PSE file, %s..." % (PSEOutfile))
  771 
  772     if not OptionsInfo["PMLOut"]:
  773         MiscUtil.PrintInfo("Deleting file %s..." % PMLOutfile)
  774         os.remove(PMLOutfile)
  775 
  776 
  777 def DeleteEmptyPyMOLObjects(OutFH, FileIndex, PyMOLObjectNames):
  778     """Delete empty PyMOL objects."""
  779 
  780     if OptionsInfo["AllowEmptyObjects"]:
  781         return
  782 
  783     SpecifiedChainsAndLigandsInfo = OptionsInfo["InfilesInfo"]["SpecifiedChainsAndLigandsInfo"][FileIndex]
  784     for ChainID in SpecifiedChainsAndLigandsInfo["ChainIDs"]:
  785         OutFH.write("""\n""\n"Checking and deleting empty objects for chain %s..."\n""\n""" % (ChainID))
  786 
  787         # Delete any chain level objects...
  788         WritePMLToCheckAndDeleteEmptyObjects(OutFH, PyMOLObjectNames["Chains"][ChainID]["Solvent"])
  789         WritePMLToCheckAndDeleteEmptyObjects(OutFH, PyMOLObjectNames["Chains"][ChainID]["Inorganic"])
  790 
  791 
  792 def DeleteEmptyPyMOLInterfaceObjects(OutFH):
  793     """Delete empty PyMOL interface objects."""
  794 
  795     if OptionsInfo["AllowEmptyObjects"]:
  796         return
  797 
  798     InterfaceChainsAndResiduesInfo = OptionsInfo["InfilesInfo"]["InterfaceChainsAndResiduesInfo"]
  799     PyMOLInterfaceObjectNamesInfo = OptionsInfo["InfilesInfo"]["PyMOLInterfaceObjectNamesInfo"]
  800 
  801     if InterfaceChainsAndResiduesInfo is None:
  802         return
  803 
  804     for InterfaceID in InterfaceChainsAndResiduesInfo["InterfaceIDs"]:
  805         for InterfaceChainID in InterfaceChainsAndResiduesInfo["InterfaceChainIDs"][InterfaceID]:
  806             ChainID = InterfaceChainsAndResiduesInfo["ChainIDs"][InterfaceID][InterfaceChainID]
  807             FileIndex = InterfaceChainsAndResiduesInfo["ChainIDsInfileIndices"][InterfaceID][InterfaceChainID]
  808 
  809             # Delete interface residue type objects...
  810             DeleteEmptyInterfaceChainResidueTypesObjects(
  811                 OutFH, FileIndex, InterfaceID, InterfaceChainID, ChainID, PyMOLInterfaceObjectNamesInfo
  812             )
  813 
  814 
  815 def DeleteEmptyInterfaceChainResidueTypesObjects(
  816     OutFH, FileIndex, InterfaceID, InterfaceChainID, ChainID, PyMOLObjectNames
  817 ):
  818     """Delete empty interface chain residue objects."""
  819 
  820     if not GetInterfaceResidueTypesStatus(FileIndex, ChainID):
  821         return
  822 
  823     ResiduesGroupIDPrefix = "ChainResidues"
  824     for GroupType in ["Aromatic", "Hydrophobic", "Polar", "Positively_Charged", "Negatively_Charged", "Other"]:
  825         GroupID = re.sub("_", "", GroupType)
  826 
  827         ResiduesGroupID = "%s%sGroup" % (ResiduesGroupIDPrefix, GroupID)
  828         GroupName = PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID][ResiduesGroupID]
  829 
  830         GroupObjectNamesList = []
  831 
  832         ResiduesObjectID = "%s%sResidues" % (ResiduesGroupIDPrefix, GroupID)
  833         ResiduesObjectName = PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID][ResiduesObjectID]
  834         GroupObjectNamesList.append(ResiduesObjectName)
  835 
  836         ResiduesSurfaceObjectID = "%s%sSurface" % (ResiduesGroupIDPrefix, GroupID)
  837         ResiduesSurfaceObjectName = PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID][
  838             ResiduesSurfaceObjectID
  839         ]
  840         GroupObjectNamesList.append(ResiduesSurfaceObjectName)
  841 
  842         GroupObjectNames = ",".join(GroupObjectNamesList)
  843         WritePMLToCheckAndDeleteEmptyObjects(OutFH, GroupObjectNames, GroupName)
  844 
  845 
  846 def WritePMLToCheckAndDeleteEmptyObjects(OutFH, ObjectName, ParentObjectName=None):
  847     """Write PML to check and delete empty PyMOL objects."""
  848 
  849     if ParentObjectName is None:
  850         PML = """CheckAndDeleteEmptyObjects("%s")""" % (ObjectName)
  851     else:
  852         PML = """CheckAndDeleteEmptyObjects("%s", "%s")""" % (ObjectName, ParentObjectName)
  853 
  854     OutFH.write("%s\n" % PML)
  855 
  856 
  857 def RetrieveInfilesInfo():
  858     """Retrieve information for input files."""
  859 
  860     InfilesInfo = {}
  861 
  862     InfilesInfo["InfilesNames"] = []
  863     InfilesInfo["InfilesRoots"] = []
  864     InfilesInfo["ChainsAndLigandsInfo"] = []
  865     InfilesInfo["SpecifiedChainsAndLigandsInfo"] = []
  866 
  867     InfilesInfo["PyMOLObjectNamesInfo"] = []
  868 
  869     InfilesInfo["SingleInfileMode"] = False
  870     InfilesInfo["InterfaceChainsAndResiduesInfo"] = None
  871     InfilesInfo["InterfaceChainPairsAndResiduesInfo"] = None
  872 
  873     InfilesInfo["PyMOLInterfaceObjectNamesInfo"] = None
  874 
  875     InfilesCount = 0
  876     for Infile in OptionsInfo["InfilesNames"]:
  877         InfilesCount += 1
  878         FileDir, FileName, FileExt = MiscUtil.ParseFileName(Infile)
  879         InfileRoot = FileName
  880 
  881         ChainsAndLigandInfo = PyMOLUtil.GetChainsAndLigandsInfo(Infile, InfileRoot)
  882 
  883         InfilesInfo["InfilesNames"].append(Infile)
  884         InfilesInfo["InfilesRoots"].append(InfileRoot)
  885         InfilesInfo["ChainsAndLigandsInfo"].append(ChainsAndLigandInfo)
  886 
  887     if InfilesCount > 2:
  888         MiscUtil.PrintError(
  889             'Number of input files, %s, specified using "-i, --infiles" option is not valid. Number of allowed files: 1 or 2'
  890             % (InfilesCount)
  891         )
  892 
  893     InfilesInfo["SingleInfileMode"] = True if InfilesCount == 1 else False
  894 
  895     OptionsInfo["InfilesInfo"] = InfilesInfo
  896 
  897 
  898 def ProcessInterfaceChainIDs():
  899     """Process specified interface chain IDs for input files."""
  900 
  901     ValidateInterfaceChainIDs()
  902 
  903     SetupChainsAndLigandsInfo()
  904     SetupPyMOLObjectNamesInfo()
  905 
  906     SetupInterfaceChainPairsAndResiduesInfo()
  907     ProcessInterfaceChainPairsAndResiduesInfo()
  908 
  909     SetupPyMOLInterfaceObjectNamesInfo()
  910 
  911 
  912 def ValidateInterfaceChainIDs():
  913     """Check for the presence of interface IDs in input file(s)."""
  914 
  915     if re.match("^auto$", OptionsInfo["InterfaceChainIDs"], re.I):
  916         AutoAssignInterfaceChainIDs()
  917         SetupInfilesInterfaceChainIDsLists()
  918         return
  919 
  920     MiscUtil.PrintInfo("\nValidating interface chain IDs...")
  921 
  922     # Check for the presences of interface chain IDs across input files..
  923     SingleInfileMode = OptionsInfo["InfilesInfo"]["SingleInfileMode"]
  924     Infile1ChainIDs = OptionsInfo["InfilesInfo"]["ChainsAndLigandsInfo"][0]["ChainIDs"]
  925     Infile2ChainIDs = []
  926     if not SingleInfileMode:
  927         Infile2ChainIDs = OptionsInfo["InfilesInfo"]["ChainsAndLigandsInfo"][1]["ChainIDs"]
  928 
  929     InterfaceChainIDsList = OptionsInfo["InterfaceChainIDsList"]
  930 
  931     for Index in range(0, len(InterfaceChainIDsList), 2):
  932         ChainIDs1 = InterfaceChainIDsList[Index]
  933         ChainIDs2 = InterfaceChainIDsList[Index + 1]
  934 
  935         for ChainID in ChainIDs1:
  936             if ChainID not in Infile1ChainIDs:
  937                 MiscUtil.PrintError(
  938                     'The chain ID, %s, specified using "-c, --chainIDs" for a chain IDs pairs, "%s,%s", must be present in first input file.'
  939                     % (ChainID, "+".join(ChainIDs1), "+".join(ChainIDs2))
  940                 )
  941 
  942         for ChainID in ChainIDs2:
  943             if SingleInfileMode:
  944                 if ChainID not in Infile1ChainIDs:
  945                     MiscUtil.PrintError(
  946                         'The chain ID, %s, specified using "-c, --chainIDs" for a chain IDs pairs, "%s,%s", must be present in first input file.'
  947                         % (ChainID, "+".join(ChainIDs1), "+".join(ChainIDs2))
  948                     )
  949             else:
  950                 if ChainID not in Infile2ChainIDs:
  951                     MiscUtil.PrintError(
  952                         'The chain ID, %s, specified using "-c, --chainIDs" for a chain IDs pairs, "%s,%s", must be present in second input file.'
  953                         % (ChainID, "+".join(ChainIDs1), "+".join(ChainIDs2))
  954                     )
  955 
  956     # Check for any duplicate interface chain IDs specifications...
  957     CanonicalInterfaceIDsMap = {}
  958 
  959     for Index in range(0, len(InterfaceChainIDsList), 2):
  960         ChainIDs1 = InterfaceChainIDsList[Index]
  961         ChainIDs2 = InterfaceChainIDsList[Index + 1]
  962         InterfaceID = "%s,%s" % ("+".join(ChainIDs1), "+".join(ChainIDs2))
  963 
  964         SortedChainIDs1 = sorted(ChainIDs1)
  965         SortedChainIDs2 = sorted(ChainIDs2)
  966         CanonicalInterfaceID = "%s,%s" % ("+".join(SortedChainIDs1), "+".join(SortedChainIDs2))
  967 
  968         if CanonicalInterfaceID in CanonicalInterfaceIDsMap:
  969             MiscUtil.PrintError(
  970                 'The chain ID pair, "%s", using "-c, --chainIDs", option has been specified multiple times, "%s".'
  971                 % (CanonicalInterfaceIDsMap[CanonicalInterfaceID], OptionsInfo["InterfaceChainIDs"])
  972             )
  973         else:
  974             CanonicalInterfaceIDsMap[CanonicalInterfaceID] = InterfaceID
  975 
  976     SetupInfilesInterfaceChainIDsLists()
  977 
  978 
  979 def SetupInfilesInterfaceChainIDsLists():
  980     """Setup interface chain IDs list for infiles."""
  981 
  982     Infie1InterfaceChainIDsList = []
  983     Infie2InterfaceChainIDsList = []
  984 
  985     SingleInfileMode = OptionsInfo["InfilesInfo"]["SingleInfileMode"]
  986     InterfaceChainIDsList = OptionsInfo["InterfaceChainIDsList"]
  987 
  988     for Index in range(0, len(InterfaceChainIDsList), 2):
  989         ChainIDs1 = InterfaceChainIDsList[Index]
  990         ChainIDs2 = InterfaceChainIDsList[Index + 1]
  991 
  992         Infie1InterfaceChainIDsList.extend(ChainIDs1)
  993         if SingleInfileMode:
  994             Infie1InterfaceChainIDsList.extend(ChainIDs2)
  995         else:
  996             Infie2InterfaceChainIDsList.extend(ChainIDs2)
  997 
  998     Infie1InterfaceChainIDsList = sorted(Infie1InterfaceChainIDsList)
  999     Infie2InterfaceChainIDsList = sorted(Infie2InterfaceChainIDsList)
 1000 
 1001     OptionsInfo["InfilesInterfaceChainIDsList"] = [Infie1InterfaceChainIDsList, Infie2InterfaceChainIDsList]
 1002 
 1003 
 1004 def AutoAssignInterfaceChainIDs():
 1005     """Handle automatic assignment of interface chain IDs."""
 1006 
 1007     Infile1ChainIDs = OptionsInfo["InfilesInfo"]["ChainsAndLigandsInfo"][0]["ChainIDs"]
 1008     Infile2ChainIDs = []
 1009     if not OptionsInfo["InfilesInfo"]["SingleInfileMode"]:
 1010         Infile2ChainIDs = OptionsInfo["InfilesInfo"]["ChainsAndLigandsInfo"][1]["ChainIDs"]
 1011 
 1012     InterfaceChainIDsList = []
 1013     if OptionsInfo["InfilesInfo"]["SingleInfileMode"]:
 1014         # Take first two chains from first input file...
 1015         if len(Infile1ChainIDs) < 2:
 1016             MiscUtil.PrintError(
 1017                 'Failed to automatically set interface chain IDs. Number of chains, %s, in input file specified using "-i, --infiles" option must be >= 2. '
 1018                 % (len(Infile1ChainIDs))
 1019             )
 1020         InterfaceChainIDsList.append([Infile1ChainIDs[0]])
 1021         InterfaceChainIDsList.append([Infile1ChainIDs[1]])
 1022     else:
 1023         # Take first chain from each input file...
 1024         if len(Infile1ChainIDs) < 1:
 1025             MiscUtil.PrintError(
 1026                 'Failed to automatically set interface chain IDs. Number of chains, %s, in first input file specified using "-i, --infiles" option must be >= 1. '
 1027                 % (len(Infile1ChainIDs))
 1028             )
 1029         if len(Infile2ChainIDs) < 1:
 1030             MiscUtil.PrintError(
 1031                 'Failed to automatically set interface chain IDs. Number of chains, %s, in second input file specified using "-i, --infiles" option must be >= 1. '
 1032                 % (len(Infile1ChainIDs))
 1033             )
 1034         InterfaceChainIDsList.append([Infile1ChainIDs[0]])
 1035         InterfaceChainIDsList.append([Infile2ChainIDs[1]])
 1036 
 1037     OptionsInfo["InterfaceChainIDsList"] = []
 1038     OptionsInfo["InterfaceChainIDsList"].extend(InterfaceChainIDsList)
 1039 
 1040     return
 1041 
 1042 
 1043 def SetupChainsAndLigandsInfo():
 1044     """Setup chains and ligands info for input files to visualize macromolecules."""
 1045 
 1046     OptionsInfo["InfilesInfo"]["SpecifiedChainsAndLigandsInfo"] = []
 1047     for FileIndex in range(0, len(OptionsInfo["InfilesInfo"]["InfilesNames"])):
 1048         Infile = OptionsInfo["InfilesInfo"]["InfilesNames"][FileIndex]
 1049         MiscUtil.PrintInfo("\nSetting up chain and ligand information for input file %s..." % Infile)
 1050 
 1051         ChainsAndLigandsInfo = OptionsInfo["InfilesInfo"]["ChainsAndLigandsInfo"][FileIndex]
 1052         InfilesInterfaceChainIDsList = OptionsInfo["InfilesInterfaceChainIDsList"][FileIndex]
 1053 
 1054         SpecifiedChainsAndLigandsInfo = {}
 1055         SpecifiedChainsAndLigandsInfo["ChainIDs"] = []
 1056         SpecifiedChainsAndLigandsInfo["InterfaceResNums"] = {}
 1057         SpecifiedChainsAndLigandsInfo["LigandIDs"] = {}
 1058 
 1059         if len(InfilesInterfaceChainIDsList):
 1060             # Add unique interface IDs to the chain IDs list for visualization. Interface chain IDs
 1061             # may contain duplicate chain IDs due to the presence # of same chain in multiple
 1062             # interfaces.
 1063             for ChainID in InfilesInterfaceChainIDsList:
 1064                 if ChainID not in SpecifiedChainsAndLigandsInfo["ChainIDs"]:
 1065                     SpecifiedChainsAndLigandsInfo["ChainIDs"].append(ChainID)
 1066 
 1067             for ChainID in SpecifiedChainsAndLigandsInfo["ChainIDs"]:
 1068                 # Initialize interface residue numbers to be assigned later...
 1069                 SpecifiedChainsAndLigandsInfo["InterfaceResNums"][ChainID] = []
 1070 
 1071                 # Setup ligand IDs...
 1072                 SpecifiedChainsAndLigandsInfo["LigandIDs"][ChainID] = SetupSpecifiedLigandIDs(
 1073                     FileIndex, ChainID, ChainsAndLigandsInfo
 1074                 )
 1075 
 1076         ProcessResidueTypesAndSurfaceOptions(FileIndex, SpecifiedChainsAndLigandsInfo)
 1077 
 1078         OptionsInfo["InfilesInfo"]["SpecifiedChainsAndLigandsInfo"].append(SpecifiedChainsAndLigandsInfo)
 1079 
 1080 
 1081 def SetupSpecifiedLigandIDs(FileIndex, ChainID, ChainsAndLigandsInfo):
 1082     """Setup specified ligand IDs for input file."""
 1083 
 1084     LigandIDs = []
 1085 
 1086     if re.match("^All$", OptionsInfo["LigandIDs"], re.I):
 1087         LigandIDs = ChainsAndLigandsInfo["LigandIDs"][ChainID]
 1088         return LigandIDs
 1089     elif re.match("^Largest|Auto$", OptionsInfo["LigandIDs"], re.I):
 1090         LargestLigandID = (
 1091             ChainsAndLigandsInfo["LigandIDs"][ChainID][0] if (len(ChainsAndLigandsInfo["LigandIDs"][ChainID])) else None
 1092         )
 1093         if LargestLigandID is not None:
 1094             LigandIDs.append(LargestLigandID)
 1095         return LigandIDs
 1096     elif re.match("^None$", OptionsInfo["LigandIDs"], re.I):
 1097         return LigandIDs
 1098 
 1099     Infile = OptionsInfo["InfilesInfo"]["InfilesNames"][FileIndex]
 1100     ValidLigandIDs = ChainsAndLigandsInfo["LigandIDs"][ChainID]
 1101 
 1102     SpecifiedLigandIDs = re.sub(" ", "", OptionsInfo["LigandIDs"])
 1103     if not SpecifiedLigandIDs:
 1104         MiscUtil.PrintError('No valid value specified using "-l, --ligandIDs" option.')
 1105 
 1106     LigandIDsWords = SpecifiedLigandIDs.split(",")
 1107     for LigandID in LigandIDsWords:
 1108         if LigandID not in ValidLigandIDs:
 1109             LigandIDsListNames = ",".join(ValidLigandIDs) if len(ValidLigandIDs) else "None"
 1110             MiscUtil.PrintWarning(
 1111                 'The ligand ID, %s, specified using "-l, --ligandiIDs" option is not valid for chain, %s, in input file, %s. It\'ll be ignored. Valid ligand IDs: %s'
 1112                 % (LigandID, ChainID, Infile, LigandIDsListNames)
 1113             )
 1114             continue
 1115 
 1116         if LigandID in LigandIDs:
 1117             MiscUtil.PrintWarning(
 1118                 'The ligand ID, %s, has already been specified using "-l, --ligandIDs" option for chain, %s, in input file, %s. It\'ll be ignored.'
 1119                 % (LigandID, ChainID, Infile)
 1120             )
 1121             continue
 1122 
 1123         LigandIDs.append(LigandID)
 1124 
 1125     if not len(LigandIDs):
 1126         MiscUtil.PrintWarning(
 1127             'No valid ligand IDs "%s" specified using "-l, --ligandIDs" option for chain ID, %s, in input file, %s.'
 1128             % (OptionsInfo["LigandIDs"], ChainID, Infile)
 1129         )
 1130 
 1131     return LigandIDs
 1132 
 1133 
 1134 def SetupInterfaceChainPairsAndResiduesInfo():
 1135     """Setup chain and residue pairs corresponding to interfaces."""
 1136 
 1137     MiscUtil.PrintInfo("\nIdentifying interface residues...")
 1138 
 1139     SingleInfileMode = OptionsInfo["InfilesInfo"]["SingleInfileMode"]
 1140     InterfaceChainIDsList = OptionsInfo["InterfaceChainIDsList"]
 1141 
 1142     if not len(InterfaceChainIDsList):
 1143         MiscUtil.PrintError("Failed to identify interface residues: No valid chain ID pairs available for interfaces")
 1144 
 1145     # Load infiles to identify interface residues...
 1146     Infile1 = OptionsInfo["InfilesInfo"]["InfilesNames"][0]
 1147     MolName1 = OptionsInfo["InfilesInfo"]["InfilesRoots"][0]
 1148     pymol.cmd.load(Infile1, MolName1)
 1149     if SingleInfileMode:
 1150         MolName2 = MolName1
 1151     else:
 1152         Infile2 = OptionsInfo["InfilesInfo"]["InfilesNames"][1]
 1153         MolName2 = OptionsInfo["InfilesInfo"]["InfilesRoots"][1]
 1154         pymol.cmd.load(Infile2, MolName2)
 1155 
 1156     # Initialize data...
 1157     InterfaceChainPairsAndResiduesInfo = {}
 1158     InterfaceChainPairsAndResiduesInfo["InterfaceIDs"] = []
 1159     InterfaceChainPairsAndResiduesInfo["ChainIDsPairs"] = {}
 1160     InterfaceChainPairsAndResiduesInfo["ChainIDsResNumsPairs"] = {}
 1161     InterfaceChainPairsAndResiduesInfo["InfileIndicesPairs"] = {}
 1162 
 1163     Method = OptionsInfo["Method"]
 1164     MethodCutoff = OptionsInfo["MethodCutoff"]
 1165     MiscUtil.PrintInfo("Methodology: %s; Cutoff: %.2f" % (Method, MethodCutoff))
 1166 
 1167     for Index in range(0, len(InterfaceChainIDsList), 2):
 1168         ChainIDs1 = sorted(InterfaceChainIDsList[Index])
 1169         ChainIDs2 = sorted(InterfaceChainIDsList[Index + 1])
 1170 
 1171         ChainIDs1Prefix = "Chains" if len(ChainIDs1) > 1 else "Chain"
 1172         ChainIDs2Prefix = "Chains" if len(ChainIDs2) > 1 else "Chain"
 1173         InterfaceID = "%s%s_%s%s" % (ChainIDs1Prefix, "+".join(ChainIDs1), ChainIDs2Prefix, "+".join(ChainIDs2))
 1174 
 1175         ListInterfaceID = "%s_%s" % ("+".join(ChainIDs1), "+".join(ChainIDs2))
 1176 
 1177         FileIndex1 = 0
 1178         FileIndex2 = 0 if SingleInfileMode else 1
 1179 
 1180         if InterfaceID in InterfaceChainPairsAndResiduesInfo["InterfaceIDs"]:
 1181             MiscUtil.PrintInfo("Ignoring interface ID %s: It has already been defined" % InterfaceID)
 1182             continue
 1183 
 1184         # Identify and list interface chains and residues...
 1185         ChainsAndResiduesInfo1, ChainsAndResiduesInfo2 = GetInterfaceChainsAndResiduesInfo(
 1186             MolName1, ChainIDs1, MolName2, ChainIDs2, Method, MethodCutoff
 1187         )
 1188         ListInterfaceChainsAndResidues(
 1189             ListInterfaceID, MolName1, ChainIDs1, ChainsAndResiduesInfo1, MolName2, ChainIDs2, ChainsAndResiduesInfo2
 1190         )
 1191 
 1192         # Check presence of interface residues...
 1193         if not (len(ChainsAndResiduesInfo1["ChainIDs"]) and len(ChainsAndResiduesInfo2["ChainIDs"])):
 1194             MiscUtil.PrintWarning(
 1195                 "Ignoring interface ID %s: Failed to identify any interface residues. PyMOL groups and objects won't be created."
 1196                 % InterfaceID
 1197             )
 1198             continue
 1199 
 1200         # Collect residue numbers for set of interface residues in each chain...
 1201         InterfaceChainIDs1, InterfaceChainResidueNums1 = GetInterfaceChainsAndResidueNumbers(
 1202             ChainIDs1, ChainsAndResiduesInfo1
 1203         )
 1204         InterfaceChainIDs2, InterfaceChainResidueNums2 = GetInterfaceChainsAndResidueNumbers(
 1205             ChainIDs2, ChainsAndResiduesInfo2
 1206         )
 1207 
 1208         InterfaceChainPairsAndResiduesInfo["InterfaceIDs"].append(InterfaceID)
 1209         InterfaceChainPairsAndResiduesInfo["ChainIDsPairs"][InterfaceID] = [InterfaceChainIDs1, InterfaceChainIDs2]
 1210         InterfaceChainPairsAndResiduesInfo["ChainIDsResNumsPairs"][InterfaceID] = [
 1211             InterfaceChainResidueNums1,
 1212             InterfaceChainResidueNums2,
 1213         ]
 1214         InterfaceChainPairsAndResiduesInfo["InfileIndicesPairs"][InterfaceID] = [FileIndex1, FileIndex2]
 1215 
 1216     InterfaceChainPairsAndResiduesInfo["InterfaceIDs"] = sorted(InterfaceChainPairsAndResiduesInfo["InterfaceIDs"])
 1217     OptionsInfo["InfilesInfo"]["InterfaceChainPairsAndResiduesInfo"] = InterfaceChainPairsAndResiduesInfo
 1218 
 1219     # Delete loaded objects...
 1220     pymol.cmd.delete(MolName1)
 1221     if not SingleInfileMode:
 1222         pymol.cmd.delete(MolName2)
 1223 
 1224 
 1225 def ProcessInterfaceChainPairsAndResiduesInfo():
 1226     """Process chain and residue pairs for visualizing interfaces."""
 1227 
 1228     InterfaceChainsAndResiduesInfo = {}
 1229     InterfaceChainsAndResiduesInfo["InterfaceIDs"] = []
 1230     InterfaceChainsAndResiduesInfo["InterfaceChainIDs"] = {}
 1231     InterfaceChainsAndResiduesInfo["ChainIDs"] = {}
 1232     InterfaceChainsAndResiduesInfo["ChainIDsResNums"] = {}
 1233     InterfaceChainsAndResiduesInfo["ChainIDsComplexNames"] = {}
 1234     InterfaceChainsAndResiduesInfo["ChainIDsInfileIndices"] = {}
 1235 
 1236     InterfaceChainPairsAndResiduesInfo = OptionsInfo["InfilesInfo"]["InterfaceChainPairsAndResiduesInfo"]
 1237 
 1238     for InterfaceID in InterfaceChainPairsAndResiduesInfo["InterfaceIDs"]:
 1239         # Flatten pairwise interface chain and residues info...
 1240         (
 1241             FlattenedInterfaceChainIDs,
 1242             FlattenedChainIDs,
 1243             FlattenedResNums,
 1244             FlattenedComplexNames,
 1245             FlattenedInfileIndices,
 1246         ) = FlattenIntferfaceChainPairsAndResiduesInfo(InterfaceChainPairsAndResiduesInfo, InterfaceID)
 1247 
 1248         if InterfaceID not in InterfaceChainsAndResiduesInfo["InterfaceIDs"]:
 1249             InterfaceChainsAndResiduesInfo["InterfaceIDs"].append(InterfaceID)
 1250 
 1251             InterfaceChainsAndResiduesInfo["InterfaceChainIDs"][InterfaceID] = []
 1252             InterfaceChainsAndResiduesInfo["ChainIDs"][InterfaceID] = {}
 1253             InterfaceChainsAndResiduesInfo["ChainIDsResNums"][InterfaceID] = {}
 1254             InterfaceChainsAndResiduesInfo["ChainIDsComplexNames"][InterfaceID] = {}
 1255             InterfaceChainsAndResiduesInfo["ChainIDsInfileIndices"][InterfaceID] = {}
 1256 
 1257         # Track interface information for visualization...
 1258         for Index in range(0, len(FlattenedInterfaceChainIDs)):
 1259             InterfaceChainID = FlattenedInterfaceChainIDs[Index]
 1260             ChainID = FlattenedChainIDs[Index]
 1261             ResNums = FlattenedResNums[Index]
 1262             ComplexName = FlattenedComplexNames[Index]
 1263             FileIndex = FlattenedInfileIndices[Index]
 1264 
 1265             if not len(ResNums):
 1266                 continue
 1267 
 1268             InterfaceChainsAndResiduesInfo["InterfaceChainIDs"][InterfaceID].append(InterfaceChainID)
 1269             InterfaceChainsAndResiduesInfo["ChainIDs"][InterfaceID][InterfaceChainID] = ChainID
 1270             InterfaceChainsAndResiduesInfo["ChainIDsResNums"][InterfaceID][InterfaceChainID] = ResNums
 1271             InterfaceChainsAndResiduesInfo["ChainIDsComplexNames"][InterfaceID][InterfaceChainID] = ComplexName
 1272             InterfaceChainsAndResiduesInfo["ChainIDsInfileIndices"][InterfaceID][InterfaceChainID] = FileIndex
 1273 
 1274             OptionsInfo["InfilesInfo"]["SpecifiedChainsAndLigandsInfo"][FileIndex]["InterfaceResNums"][ChainID].extend(
 1275                 ResNums
 1276             )
 1277 
 1278     OptionsInfo["InfilesInfo"]["InterfaceChainsAndResiduesInfo"] = InterfaceChainsAndResiduesInfo
 1279 
 1280 
 1281 def FlattenIntferfaceChainPairsAndResiduesInfo(InterfaceChainPairsAndResiduesInfo, InterfaceID):
 1282     """Flatten interface chain and residues info."""
 1283 
 1284     SingleInfileMode = OptionsInfo["InfilesInfo"]["SingleInfileMode"]
 1285 
 1286     ChainIDs1, ChainIDs2 = InterfaceChainPairsAndResiduesInfo["ChainIDsPairs"][InterfaceID]
 1287     ResNums1, ResNums2 = InterfaceChainPairsAndResiduesInfo["ChainIDsResNumsPairs"][InterfaceID]
 1288     FileIndex1, FileIndex2 = InterfaceChainPairsAndResiduesInfo["InfileIndicesPairs"][InterfaceID]
 1289 
 1290     # Set complex names..
 1291     PDBGroup1 = OptionsInfo["InfilesInfo"]["PyMOLObjectNamesInfo"][FileIndex1]["PDBGroup"]
 1292     ComplexName1 = OptionsInfo["InfilesInfo"]["PyMOLObjectNamesInfo"][FileIndex1]["Complex"]
 1293 
 1294     PDBGroup2 = OptionsInfo["InfilesInfo"]["PyMOLObjectNamesInfo"][FileIndex2]["PDBGroup"]
 1295     ComplexName2 = OptionsInfo["InfilesInfo"]["PyMOLObjectNamesInfo"][FileIndex2]["Complex"]
 1296 
 1297     ChainIDs = []
 1298     InterfaceChainIDs = []
 1299     ResNums = []
 1300     ComplexNames = []
 1301     FileIndices = []
 1302 
 1303     for ChainID in ChainIDs1:
 1304         ChainIDs.append(ChainID)
 1305         InterfaceChainID = ("Chain%s" % ChainID) if SingleInfileMode else ("Chain%s_%s" % (ChainID, PDBGroup1))
 1306         InterfaceChainIDs.append(InterfaceChainID)
 1307         ResNums.append(ResNums1["ResNums"][ChainID])
 1308         ComplexNames.append(ComplexName1)
 1309         FileIndices.append(FileIndex1)
 1310 
 1311     for ChainID in ChainIDs2:
 1312         ChainIDs.append(ChainID)
 1313         InterfaceChainID = ("Chain%s" % ChainID) if SingleInfileMode else ("Chain%s_%s" % (ChainID, PDBGroup2))
 1314         InterfaceChainIDs.append(InterfaceChainID)
 1315         ResNums.append(ResNums2["ResNums"][ChainID])
 1316         ComplexNames.append(ComplexName2)
 1317         FileIndices.append(FileIndex2)
 1318 
 1319     return InterfaceChainIDs, ChainIDs, ResNums, ComplexNames, FileIndices
 1320 
 1321 
 1322 def GetInterfaceChainsAndResiduesInfo(MolName1, ChainIDs1, MolName2, ChainIDs2, Method, Cutoff):
 1323     """Get interface chains and residues info for chains using a specified methodology."""
 1324 
 1325     InterfaceChainsResiduesInfo1 = None
 1326     InterfaceChainsResiduesInfo2 = None
 1327 
 1328     ChainNames1 = ",".join(ChainIDs1)
 1329     ChainNames2 = ",".join(ChainIDs2)
 1330 
 1331     if re.match("^BySASAChange$", Method, re.I):
 1332         InterfaceChainsResiduesInfo1, InterfaceChainsResiduesInfo2 = PyMOLUtil.GetInterfaceChainsResiduesBySASAChange(
 1333             MolName1, ChainNames1, MolName2, ChainNames2, Cutoff
 1334         )
 1335     elif re.match("^ByHeavyAtomsDistance$", Method, re.I):
 1336         InterfaceChainsResiduesInfo1, InterfaceChainsResiduesInfo2 = (
 1337             PyMOLUtil.GetInterfaceChainsResiduesByHeavyAtomsDistance(
 1338                 MolName1, ChainNames1, MolName2, ChainNames2, Cutoff
 1339             )
 1340         )
 1341     elif re.match("^ByCAlphaAtomsDistance$", Method, re.I):
 1342         InterfaceChainsResiduesInfo1, InterfaceChainsResiduesInfo2 = (
 1343             PyMOLUtil.GetInterfaceChainsResiduesByCAlphaAtomsDistance(
 1344                 MolName1, ChainNames1, MolName2, ChainNames2, Cutoff
 1345             )
 1346         )
 1347     else:
 1348         MiscUtil.PrintError("Failed to retrieve interface residues information: Method %s is not valid..." % Method)
 1349 
 1350     return InterfaceChainsResiduesInfo1, InterfaceChainsResiduesInfo2
 1351 
 1352 
 1353 def ListInterfaceChainsAndResidues(
 1354     InterfaceID, MolName1, ChainIDs1, ChainsAndResiduesInfo1, MolName2, ChainIDs2, ChainsAndResiduesInfo2
 1355 ):
 1356     """List interface chains and residues for an interface."""
 1357 
 1358     ChainNames1, ResiduesInfo1 = PrepareInterfaceChainsAndResiduesInfo(ChainsAndResiduesInfo1)
 1359     ChainNames2, ResiduesInfo2 = PrepareInterfaceChainsAndResiduesInfo(ChainsAndResiduesInfo2)
 1360 
 1361     if len(ChainNames1) and len(ChainNames2):
 1362         MiscUtil.PrintInfo("\nListing interface residues for interface chain IDs: %s" % (InterfaceID))
 1363 
 1364         ListInterfaceChainsAndResiduesInfo(InterfaceID, MolName1, ChainIDs1, ChainNames1, ResiduesInfo1)
 1365         ListInterfaceChainsAndResiduesInfo(InterfaceID, MolName2, ChainIDs2, ChainNames2, ResiduesInfo2)
 1366     else:
 1367         MiscUtil.PrintInfo("\nListing interface residues for interface chain IDs: %s" % (InterfaceID))
 1368         MiscUtil.PrintInfo("Interface chain IDs: None; ChainID: None; Number of interface residues: 0")
 1369 
 1370 
 1371 def PrepareInterfaceChainsAndResiduesInfo(ChainsAndResiduesInfo):
 1372     """Prepare interface chains and residues info for listing."""
 1373 
 1374     ChainNames = []
 1375     ResiduesInfo = []
 1376 
 1377     for ChainID in ChainsAndResiduesInfo["ChainIDs"]:
 1378         ChainNames.append(ChainID)
 1379 
 1380         # Setup distribution of residues...
 1381         LineWords = []
 1382         ResiduesCount = 0
 1383         SortedResNames = sorted(
 1384             ChainsAndResiduesInfo["ResNames"][ChainID],
 1385             key=lambda ResName: ChainsAndResiduesInfo["ResCount"][ChainID][ResName],
 1386             reverse=True,
 1387         )
 1388         for ResName in SortedResNames:
 1389             ResCount = ChainsAndResiduesInfo["ResCount"][ChainID][ResName]
 1390             LineWords.append("%s - %s" % (ResName, ResCount))
 1391             ResiduesCount += ResCount
 1392 
 1393         ResiduesDistribution = "; ".join(LineWords) if len(LineWords) else None
 1394 
 1395         # Setup residue IDs sorted by residue numbers...
 1396         ResNumMap = {}
 1397         for ResName in ChainsAndResiduesInfo["ResNames"][ChainID]:
 1398             for ResNum in ChainsAndResiduesInfo["ResNum"][ChainID][ResName]:
 1399                 ResNumMap[ResNum] = ResName
 1400 
 1401         LineWords = []
 1402         for ResNum in sorted(ResNumMap, key=int):
 1403             ResName = ResNumMap[ResNum]
 1404             ResID = "%s_%s" % (ResName, ResNum)
 1405             LineWords.append(ResID)
 1406         ResiduesIDs = ", ".join(LineWords) if len(LineWords) else None
 1407 
 1408         ResiduesInfo.append([ResiduesCount, ResiduesDistribution, ResiduesIDs])
 1409 
 1410     return ChainNames, ResiduesInfo
 1411 
 1412 
 1413 def ListInterfaceChainsAndResiduesInfo(InterfaceID, MolName, InterfaceChainIDs, ChainNames, ResiduesInfo):
 1414     """List interface chains and residues."""
 1415 
 1416     for ChainID in InterfaceChainIDs:
 1417         if ChainID not in ChainNames:
 1418             MiscUtil.PrintWarning(
 1419                 "Interface chain IDs: %s; MoleculeID: %s; ChainID: %s; Number of interface residues: 0. PyMOL groups and objects related to chain won't be created."
 1420                 % (InterfaceID, MolName, ChainID)
 1421             )
 1422             continue
 1423 
 1424         for Index in range(0, len(ChainNames)):
 1425             ChainName = ChainNames[Index]
 1426             ChainResiduesInfo = ResiduesInfo[Index]
 1427             if re.match(ChainID, ChainName, re.I):
 1428                 MiscUtil.PrintInfo(
 1429                     "\nInterface chain IDs: %s; MoleculeID: %s; ChainID: %s; Number of interface residues: %d"
 1430                     % (InterfaceID, MolName, ChainName, ChainResiduesInfo[0])
 1431                 )
 1432                 MiscUtil.PrintInfo(
 1433                     "Residue distribution: %s\nResidue IDs: %s" % (ChainResiduesInfo[1], ChainResiduesInfo[2])
 1434                 )
 1435                 continue
 1436 
 1437 
 1438 def GetInterfaceChainsAndResidueNumbers(ChainIDs, ChainsAndResiduesInfo):
 1439     """Collect interface residue numbers for chains."""
 1440 
 1441     ChainResidueNums = {}
 1442     ChainResidueNums["ChainIDs"] = []
 1443     ChainResidueNums["ResNums"] = {}
 1444 
 1445     for ChainID in ChainIDs:
 1446         if ChainID not in ChainsAndResiduesInfo["ChainIDs"]:
 1447             continue
 1448 
 1449         ChainResidueNums["ChainIDs"].append(ChainID)
 1450         ChainResidueNums["ResNums"][ChainID] = []
 1451 
 1452         ResNums = []
 1453         for ResName in ChainsAndResiduesInfo["ResNames"][ChainID]:
 1454             ResNums.extend(ChainsAndResiduesInfo["ResNum"][ChainID][ResName])
 1455 
 1456         ChainResidueNums["ResNums"][ChainID] = sorted(ResNums, key=int)
 1457 
 1458     InterfaceChainIDs = ChainResidueNums["ChainIDs"]
 1459 
 1460     return InterfaceChainIDs, ChainResidueNums
 1461 
 1462 
 1463 def SetupPyMOLObjectNamesInfo():
 1464     """Setup PyMOL object names for displaying macromolecules."""
 1465 
 1466     OptionsInfo["InfilesInfo"]["PyMOLObjectNamesInfo"] = []
 1467 
 1468     for FileIndex in range(0, len(OptionsInfo["InfilesInfo"]["InfilesNames"])):
 1469         PyMOLObjectNamesInfo = SetupPyMOLObjectNames(FileIndex)
 1470         OptionsInfo["InfilesInfo"]["PyMOLObjectNamesInfo"].append(PyMOLObjectNamesInfo)
 1471 
 1472 
 1473 def SetupPyMOLObjectNames(FileIndex):
 1474     """Setup hierarchy of PyMOL groups and objects for ligand centric views of
 1475     chains and ligands present in input file.
 1476     """
 1477 
 1478     PyMOLObjectNames = {}
 1479     PyMOLObjectNames["Chains"] = {}
 1480     PyMOLObjectNames["Ligands"] = {}
 1481 
 1482     # Setup groups and objects for complex...
 1483     SetupPyMOLObjectNamesForComplex(FileIndex, PyMOLObjectNames)
 1484 
 1485     # Setup groups and objects for chain...
 1486     SpecifiedChainsAndLigandsInfo = OptionsInfo["InfilesInfo"]["SpecifiedChainsAndLigandsInfo"][FileIndex]
 1487     for ChainID in SpecifiedChainsAndLigandsInfo["ChainIDs"]:
 1488         SetupPyMOLObjectNamesForChain(FileIndex, PyMOLObjectNames, ChainID)
 1489 
 1490         # Setup groups and objects for ligand...
 1491         for LigandID in SpecifiedChainsAndLigandsInfo["LigandIDs"][ChainID]:
 1492             SetupPyMOLObjectNamesForLigand(FileIndex, PyMOLObjectNames, ChainID, LigandID)
 1493 
 1494     return PyMOLObjectNames
 1495 
 1496 
 1497 def SetupPyMOLObjectNamesForComplex(FileIndex, PyMOLObjectNames):
 1498     """Setup groups and objects for complex."""
 1499 
 1500     PDBFileRoot = OptionsInfo["InfilesInfo"]["InfilesRoots"][FileIndex]
 1501 
 1502     PDBGroupName = "%s" % PDBFileRoot
 1503     PyMOLObjectNames["PDBGroup"] = PDBGroupName
 1504     PyMOLObjectNames["PDBGroupMembers"] = []
 1505 
 1506     ComplexGroupName = "%s.Complex" % PyMOLObjectNames["PDBGroup"]
 1507     PyMOLObjectNames["ComplexGroup"] = ComplexGroupName
 1508     PyMOLObjectNames["PDBGroupMembers"].append(ComplexGroupName)
 1509 
 1510     PyMOLObjectNames["Complex"] = "%s.Complex" % ComplexGroupName
 1511     if OptionsInfo["SurfaceComplex"]:
 1512         PyMOLObjectNames["ComplexHydrophobicSurface"] = "%s.Surface" % ComplexGroupName
 1513 
 1514     PyMOLObjectNames["ComplexGroupMembers"] = []
 1515     PyMOLObjectNames["ComplexGroupMembers"].append(PyMOLObjectNames["Complex"])
 1516     if OptionsInfo["SurfaceComplex"]:
 1517         PyMOLObjectNames["ComplexGroupMembers"].append(PyMOLObjectNames["ComplexHydrophobicSurface"])
 1518 
 1519 
 1520 def SetupPyMOLObjectNamesForChain(FileIndex, PyMOLObjectNames, ChainID):
 1521     """Setup groups and objects for chain."""
 1522 
 1523     PDBGroupName = PyMOLObjectNames["PDBGroup"]
 1524 
 1525     PyMOLObjectNames["Chains"][ChainID] = {}
 1526     PyMOLObjectNames["Ligands"][ChainID] = {}
 1527 
 1528     # Set up chain group and chain objects...
 1529     ChainGroupName = "%s.Chain%s" % (PDBGroupName, ChainID)
 1530     PyMOLObjectNames["Chains"][ChainID]["ChainGroup"] = ChainGroupName
 1531     PyMOLObjectNames["PDBGroupMembers"].append(ChainGroupName)
 1532     PyMOLObjectNames["Chains"][ChainID]["ChainGroupMembers"] = []
 1533 
 1534     # Setup chain complex group and objects...
 1535     ChainComplexGroupName = "%s.Complex" % (ChainGroupName)
 1536     PyMOLObjectNames["Chains"][ChainID]["ChainComplexGroup"] = ChainComplexGroupName
 1537     PyMOLObjectNames["Chains"][ChainID]["ChainGroupMembers"].append(ChainComplexGroupName)
 1538 
 1539     PyMOLObjectNames["Chains"][ChainID]["ChainComplexGroupMembers"] = []
 1540 
 1541     Name = "%s.Complex" % (ChainComplexGroupName)
 1542     PyMOLObjectNames["Chains"][ChainID]["ChainComplex"] = Name
 1543     PyMOLObjectNames["Chains"][ChainID]["ChainComplexGroupMembers"].append(Name)
 1544 
 1545     if OptionsInfo["SurfaceChainComplex"]:
 1546         Name = "%s.Surface" % (ChainComplexGroupName)
 1547         PyMOLObjectNames["Chains"][ChainID]["ChainComplexHydrophobicSurface"] = Name
 1548         PyMOLObjectNames["Chains"][ChainID]["ChainComplexGroupMembers"].append(Name)
 1549 
 1550     # Setup up a group for individual chains...
 1551     ChainAloneGroupName = "%s.Chain" % (ChainGroupName)
 1552     PyMOLObjectNames["Chains"][ChainID]["ChainAloneGroup"] = ChainAloneGroupName
 1553     PyMOLObjectNames["Chains"][ChainID]["ChainGroupMembers"].append(ChainAloneGroupName)
 1554 
 1555     PyMOLObjectNames["Chains"][ChainID]["ChainAloneGroupMembers"] = []
 1556 
 1557     Name = "%s.Chain" % (ChainAloneGroupName)
 1558     PyMOLObjectNames["Chains"][ChainID]["ChainAlone"] = Name
 1559     PyMOLObjectNames["Chains"][ChainID]["ChainAloneGroupMembers"].append(Name)
 1560 
 1561     # Setup a group for non-interface residues...
 1562     NonInterfaceGroupName = "%s.NonInterface" % (ChainAloneGroupName)
 1563     PyMOLObjectNames["Chains"][ChainID]["ChainAloneNonInterfaceGroup"] = NonInterfaceGroupName
 1564     PyMOLObjectNames["Chains"][ChainID]["ChainAloneGroupMembers"].append(NonInterfaceGroupName)
 1565 
 1566     PyMOLObjectNames["Chains"][ChainID]["ChainAloneNonInterfaceGroupMembers"] = []
 1567 
 1568     # Setup a chain for non-interface residues...
 1569     Name = "%s.Chain" % (NonInterfaceGroupName)
 1570     PyMOLObjectNames["Chains"][ChainID]["ChainAloneNonInterface"] = Name
 1571     PyMOLObjectNames["Chains"][ChainID]["ChainAloneNonInterfaceGroupMembers"].append(Name)
 1572 
 1573     if GetChainAloneContainsSurfacesStatus(FileIndex, ChainID):
 1574         # Setup a surface group and add it to chain alone non-interface group...
 1575         SurfaceGroupName = "%s.Surface" % (NonInterfaceGroupName)
 1576         PyMOLObjectNames["Chains"][ChainID]["ChainAloneSurfaceGroup"] = SurfaceGroupName
 1577         PyMOLObjectNames["Chains"][ChainID]["ChainAloneNonInterfaceGroupMembers"].append(SurfaceGroupName)
 1578 
 1579         PyMOLObjectNames["Chains"][ChainID]["ChainAloneSurfaceGroupMembers"] = []
 1580 
 1581         # Setup a generic colored surface...
 1582         Name = "%s.Surface" % (SurfaceGroupName)
 1583         PyMOLObjectNames["Chains"][ChainID]["ChainAloneSurface"] = Name
 1584         PyMOLObjectNames["Chains"][ChainID]["ChainAloneSurfaceGroupMembers"].append(Name)
 1585 
 1586         if GetChainAloneSurfaceChainStatus(FileIndex, ChainID):
 1587             # Setup hydrophobicity surface...
 1588             Name = "%s.Hydrophobicity" % (SurfaceGroupName)
 1589             PyMOLObjectNames["Chains"][ChainID]["ChainAloneHydrophobicSurface"] = Name
 1590             PyMOLObjectNames["Chains"][ChainID]["ChainAloneSurfaceGroupMembers"].append(Name)
 1591 
 1592             # Setup hydrophobicity and charge surface...
 1593             Name = "%s.Hydrophobicity_Charge" % (SurfaceGroupName)
 1594             PyMOLObjectNames["Chains"][ChainID]["ChainAloneHydrophobicChargeSurface"] = Name
 1595             PyMOLObjectNames["Chains"][ChainID]["ChainAloneSurfaceGroupMembers"].append(Name)
 1596 
 1597         if GetChainAloneSurfaceChainElectrostaticsStatus(FileIndex, ChainID):
 1598             # Setup electrostatics group...
 1599             GroupName = "%s.Vacuum_Electrostatics" % (SurfaceGroupName)
 1600             PyMOLObjectNames["Chains"][ChainID]["ChainAloneElectrostaticsGroup"] = GroupName
 1601             PyMOLObjectNames["Chains"][ChainID]["ChainAloneSurfaceGroupMembers"].append(GroupName)
 1602 
 1603             # Setup electrostatics group members...
 1604             PyMOLObjectNames["Chains"][ChainID]["ChainAloneElectrostaticsGroupMembers"] = []
 1605 
 1606             for MemberType in ["Chain", "Contact_Potential", "Map", "Legend", "Volume"]:
 1607                 MemberID = re.sub("_", "", MemberType)
 1608 
 1609                 Name = "%s.%s" % (GroupName, MemberType)
 1610                 NameID = "ChainAloneElectrostaticsSurface%s" % MemberID
 1611 
 1612                 PyMOLObjectNames["Chains"][ChainID][NameID] = Name
 1613                 PyMOLObjectNames["Chains"][ChainID]["ChainAloneElectrostaticsGroupMembers"].append(Name)
 1614 
 1615     # Setup solvent and inorganic objects for chain...
 1616     for NameID in ["Solvent", "Inorganic"]:
 1617         Name = "%s.%s" % (ChainGroupName, NameID)
 1618         PyMOLObjectNames["Chains"][ChainID][NameID] = Name
 1619         PyMOLObjectNames["Chains"][ChainID]["ChainGroupMembers"].append(Name)
 1620 
 1621 
 1622 def SetupPyMOLObjectNamesForLigand(FileIndex, PyMOLObjectNames, ChainID, LigandID):
 1623     """Stetup groups and objects for ligand."""
 1624 
 1625     PyMOLObjectNames["Ligands"][ChainID][LigandID] = {}
 1626 
 1627     ChainGroupName = PyMOLObjectNames["Chains"][ChainID]["ChainGroup"]
 1628 
 1629     # Setup a chain level ligand group...
 1630     ChainLigandGroupName = "%s.Ligand%s" % (ChainGroupName, LigandID)
 1631     PyMOLObjectNames["Ligands"][ChainID][LigandID]["ChainLigandGroup"] = ChainLigandGroupName
 1632     PyMOLObjectNames["Chains"][ChainID]["ChainGroupMembers"].append(ChainLigandGroupName)
 1633 
 1634     PyMOLObjectNames["Ligands"][ChainID][LigandID]["ChainLigandGroupMembers"] = []
 1635 
 1636     # Set up ligand group and its members...
 1637     GroupName = "%s.Ligand" % (ChainLigandGroupName)
 1638     GroupNameID = "LigandGroup"
 1639     GroupMembersID = "LigandGroupMembers"
 1640 
 1641     PyMOLObjectNames["Ligands"][ChainID][LigandID][GroupNameID] = GroupName
 1642     PyMOLObjectNames["Ligands"][ChainID][LigandID]["ChainLigandGroupMembers"].append(GroupName)
 1643 
 1644     LigandName = "%s.Ligand" % (GroupName)
 1645     LigandNameID = "Ligand"
 1646     PyMOLObjectNames["Ligands"][ChainID][LigandID][LigandNameID] = LigandName
 1647 
 1648     PyMOLObjectNames["Ligands"][ChainID][LigandID][GroupMembersID] = []
 1649     PyMOLObjectNames["Ligands"][ChainID][LigandID][GroupMembersID].append(LigandName)
 1650 
 1651     # Add ball and stick...
 1652     BallAndStickName = "%s.BallAndStick" % (GroupName)
 1653     BallAndStickID = "LigandBallAndStick"
 1654     PyMOLObjectNames["Ligands"][ChainID][LigandID][BallAndStickID] = BallAndStickName
 1655     PyMOLObjectNames["Ligands"][ChainID][LigandID][GroupMembersID].append(BallAndStickName)
 1656 
 1657 
 1658 def SetupPyMOLInterfaceObjectNamesInfo():
 1659     """Setup PyMOL object names for displaying macromolecular interfaces."""
 1660 
 1661     InterfaceChainsAndResiduesInfo = OptionsInfo["InfilesInfo"]["InterfaceChainsAndResiduesInfo"]
 1662 
 1663     PyMOLObjectNames = {}
 1664     PyMOLObjectNames["InterfaceIDs"] = {}
 1665     PyMOLObjectNames["InterfaceChainIDs"] = {}
 1666 
 1667     # Setup top level interfaces group...
 1668     InterfacesGroupName = "Interfaces"
 1669     PyMOLObjectNames["InterfacesGroup"] = InterfacesGroupName
 1670     PyMOLObjectNames["InterfacesGroupMembers"] = []
 1671 
 1672     for InterfaceID in InterfaceChainsAndResiduesInfo["InterfaceIDs"]:
 1673         PyMOLObjectNames["InterfaceIDs"][InterfaceID] = {}
 1674         PyMOLObjectNames["InterfaceChainIDs"][InterfaceID] = {}
 1675 
 1676         # Setup an interface group...
 1677         InterfaceIDGroupName = "%s.%s" % (InterfacesGroupName, InterfaceID)
 1678         PyMOLObjectNames["InterfaceIDs"][InterfaceID]["InterfaceIDGroup"] = InterfaceIDGroupName
 1679         PyMOLObjectNames["InterfacesGroupMembers"].append(InterfaceIDGroupName)
 1680         PyMOLObjectNames["InterfaceIDs"][InterfaceID]["InterfaceIDGroupMembers"] = []
 1681 
 1682         # Setup a polar contact group...
 1683         if OptionsInfo["InterfacePolarContacts"]:
 1684             PolarContactName = "%s.Polar_Contacts" % (InterfaceIDGroupName)
 1685             PyMOLObjectNames["InterfaceIDs"][InterfaceID]["PolarContacts"] = PolarContactName
 1686             PyMOLObjectNames["InterfaceIDs"][InterfaceID]["InterfaceIDGroupMembers"].append(PolarContactName)
 1687 
 1688         # Setup a hydrophobic contact group...
 1689         if OptionsInfo["InterfaceHydrophobicContacts"]:
 1690             HydrophobicContactsName = "%s.Hydrophobic_Contacts" % (InterfaceIDGroupName)
 1691             PyMOLObjectNames["InterfaceIDs"][InterfaceID]["HydrophobicContacts"] = HydrophobicContactsName
 1692             PyMOLObjectNames["InterfaceIDs"][InterfaceID]["InterfaceIDGroupMembers"].append(HydrophobicContactsName)
 1693 
 1694         for InterfaceChainID in InterfaceChainsAndResiduesInfo["InterfaceChainIDs"][InterfaceID]:
 1695             SetupPyMOLInterfaceObjectNamesForChain(PyMOLObjectNames, InterfaceID, InterfaceChainID)
 1696 
 1697     OptionsInfo["InfilesInfo"]["PyMOLInterfaceObjectNamesInfo"] = PyMOLObjectNames
 1698 
 1699 
 1700 def SetupPyMOLInterfaceObjectNamesForChain(PyMOLObjectNames, InterfaceID, InterfaceChainID):
 1701     """Setup PyMOL interface object names for a chain."""
 1702 
 1703     InterfaceChainsAndResiduesInfo = OptionsInfo["InfilesInfo"]["InterfaceChainsAndResiduesInfo"]
 1704     FileIndex = InterfaceChainsAndResiduesInfo["ChainIDsInfileIndices"][InterfaceID][InterfaceChainID]
 1705     ChainID = InterfaceChainsAndResiduesInfo["ChainIDs"][InterfaceID][InterfaceChainID]
 1706 
 1707     InterfaceIDGroupName = PyMOLObjectNames["InterfaceIDs"][InterfaceID]["InterfaceIDGroup"]
 1708 
 1709     PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID] = {}
 1710 
 1711     # Setup a chain group...
 1712     ChainGroupName = "%s.%s" % (InterfaceIDGroupName, InterfaceChainID)
 1713     PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainGroup"] = ChainGroupName
 1714     PyMOLObjectNames["InterfaceIDs"][InterfaceID]["InterfaceIDGroupMembers"].append(ChainGroupName)
 1715     PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainGroupMembers"] = []
 1716 
 1717     # Setup chain...
 1718     Name = "%s.Chain" % (ChainGroupName)
 1719     PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["Chain"] = Name
 1720     PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainGroupMembers"].append(Name)
 1721 
 1722     if GetInterfaceResidueTypesStatus(FileIndex, ChainID):
 1723         # Setup residue type group and its subgroups...
 1724         ResiduesGroupName = "%s.Residues" % (ChainGroupName)
 1725 
 1726         ResiduesGroupIDPrefix = "ChainResidues"
 1727         ResiduesGroupID = "%sGroup" % ResiduesGroupIDPrefix
 1728 
 1729         # Add residue group to chain group...
 1730         PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID][ResiduesGroupID] = ResiduesGroupName
 1731         PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainGroupMembers"].append(
 1732             ResiduesGroupName
 1733         )
 1734 
 1735         # Initialize residue group members...
 1736         ResiduesGroupMembersID = "%sGroupMembers" % ResiduesGroupIDPrefix
 1737         PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID][ResiduesGroupMembersID] = []
 1738 
 1739         # Setup residues sub groups and its members...
 1740         for SubGroupType in ["Aromatic", "Hydrophobic", "Polar", "Positively_Charged", "Negatively_Charged", "Other"]:
 1741             SubGroupID = re.sub("_", "", SubGroupType)
 1742 
 1743             ResiduesSubGroupName = "%s.%s" % (ResiduesGroupName, SubGroupType)
 1744             ResiduesSubGroupID = "%s%sGroup" % (ResiduesGroupIDPrefix, SubGroupID)
 1745 
 1746             # Add sub group to residues group...
 1747             PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID][ResiduesSubGroupID] = (
 1748                 ResiduesSubGroupName
 1749             )
 1750             PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID][ResiduesGroupMembersID].append(
 1751                 ResiduesSubGroupName
 1752             )
 1753 
 1754             # Initialize sub group members...
 1755             ResiduesSubGroupMembersID = "%s%sGroupMembers" % (ResiduesGroupIDPrefix, SubGroupID)
 1756             PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID][ResiduesSubGroupMembersID] = []
 1757 
 1758             # Add sub group members to subgroup...
 1759             for MemberType in ["Residues", "Surface"]:
 1760                 MemberID = re.sub("_", "", MemberType)
 1761 
 1762                 SubGroupMemberName = "%s.%s" % (ResiduesSubGroupName, MemberType)
 1763                 SubGroupMemberID = "%s%s%s" % (ResiduesGroupIDPrefix, SubGroupID, MemberID)
 1764 
 1765                 PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID][SubGroupMemberID] = (
 1766                     SubGroupMemberName
 1767                 )
 1768                 PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID][ResiduesSubGroupMembersID].append(
 1769                     SubGroupMemberName
 1770                 )
 1771 
 1772     if GetInterfaceContainsSurfacesStatus(FileIndex, ChainID):
 1773         # Setup a surface group and add it to chain group...
 1774         SurfaceGroupName = "%s.Surface" % (ChainGroupName)
 1775         PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainSurfaceGroup"] = SurfaceGroupName
 1776         PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainGroupMembers"].append(
 1777             SurfaceGroupName
 1778         )
 1779 
 1780         PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainSurfaceGroupMembers"] = []
 1781 
 1782         # Setup a generic colored surface...
 1783         Name = "%s.Surface" % (SurfaceGroupName)
 1784         PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainSurface"] = Name
 1785         PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainSurfaceGroupMembers"].append(Name)
 1786 
 1787         if GetInterfaceSurfaceChainStatus(FileIndex, ChainID):
 1788             # Setup hydrophobicity surface...
 1789             Name = "%s.Hydrophobicity" % (SurfaceGroupName)
 1790             PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainHydrophobicSurface"] = Name
 1791             PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainSurfaceGroupMembers"].append(
 1792                 Name
 1793             )
 1794 
 1795             # Setup hydrophobicity and charge surface...
 1796             Name = "%s.Hydrophobicity_Charge" % (SurfaceGroupName)
 1797             PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainHydrophobicChargeSurface"] = Name
 1798             PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainSurfaceGroupMembers"].append(
 1799                 Name
 1800             )
 1801 
 1802         if GetInterfaceSurfaceChainElectrostaticsStatus(FileIndex, ChainID):
 1803             # Setup electrostatics group...
 1804             GroupName = "%s.Vacuum_Electrostatics" % (SurfaceGroupName)
 1805             PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainElectrostaticsGroup"] = GroupName
 1806             PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainSurfaceGroupMembers"].append(
 1807                 GroupName
 1808             )
 1809 
 1810             # Setup electrostatics group members...
 1811             PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID]["ChainElectrostaticsGroupMembers"] = []
 1812 
 1813             for MemberType in ["Chain", "Contact_Potential", "Map", "Legend", "Volume"]:
 1814                 MemberID = re.sub("_", "", MemberType)
 1815 
 1816                 Name = "%s.%s" % (GroupName, MemberType)
 1817                 NameID = "ChainElectrostaticsSurface%s" % MemberID
 1818 
 1819                 PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID][NameID] = Name
 1820                 PyMOLObjectNames["InterfaceChainIDs"][InterfaceID][InterfaceChainID][
 1821                     "ChainElectrostaticsGroupMembers"
 1822                 ].append(Name)
 1823 
 1824 
 1825 def ProcessResidueTypesAndSurfaceOptions(FileIndex, SpecifiedChainsAndLigandsInfo):
 1826     """Process residue types and surface options for chains and interfaces."""
 1827 
 1828     SpecifiedChainsAndLigandsInfo["ChainSurfaces"] = {}
 1829     SpecifiedChainsAndLigandsInfo["SurfaceChain"] = {}
 1830     SpecifiedChainsAndLigandsInfo["SurfaceChainElectrostatics"] = {}
 1831 
 1832     SpecifiedChainsAndLigandsInfo["InterfaceSurfaces"] = {}
 1833     SpecifiedChainsAndLigandsInfo["SurfaceInterface"] = {}
 1834     SpecifiedChainsAndLigandsInfo["SurfaceInterfaceElectrostatics"] = {}
 1835 
 1836     SpecifiedChainsAndLigandsInfo["ResidueTypesInterface"] = {}
 1837 
 1838     # Load infile...
 1839     Infile = OptionsInfo["InfilesInfo"]["InfilesNames"][FileIndex]
 1840     MolName = OptionsInfo["InfilesInfo"]["InfilesRoots"][FileIndex]
 1841     pymol.cmd.load(Infile, MolName)
 1842 
 1843     for ChainID in SpecifiedChainsAndLigandsInfo["ChainIDs"]:
 1844         AminoAcidsPresent = PyMOLUtil.AreAminoAcidResiduesPresent(MolName, ChainID)
 1845 
 1846         # Process surfaces for chains...
 1847         if re.match("^auto$", OptionsInfo["SurfaceChain"], re.I):
 1848             SurfaceChain = True if AminoAcidsPresent else False
 1849         else:
 1850             SurfaceChain = True if re.match("^yes$", OptionsInfo["SurfaceChain"], re.I) else False
 1851         SpecifiedChainsAndLigandsInfo["SurfaceChain"][ChainID] = SurfaceChain
 1852 
 1853         if re.match("^auto$", OptionsInfo["SurfaceChainElectrostatics"], re.I):
 1854             SurfaceChainElectrostatics = True if AminoAcidsPresent else False
 1855         else:
 1856             SurfaceChainElectrostatics = (
 1857                 True if re.match("^yes$", OptionsInfo["SurfaceChainElectrostatics"], re.I) else False
 1858             )
 1859         SpecifiedChainsAndLigandsInfo["SurfaceChainElectrostatics"][ChainID] = SurfaceChainElectrostatics
 1860 
 1861         # A colored surface is always created by default...
 1862         ChainSurfaces = True
 1863         SpecifiedChainsAndLigandsInfo["ChainSurfaces"][ChainID] = ChainSurfaces
 1864 
 1865         # Process residue types and surfaces for interfaces...
 1866         if re.match("^auto$", OptionsInfo["InterfaceSurface"], re.I):
 1867             InterfaceSurface = True if AminoAcidsPresent else False
 1868         else:
 1869             InterfaceSurface = True if re.match("^yes$", OptionsInfo["InterfaceSurface"], re.I) else False
 1870         SpecifiedChainsAndLigandsInfo["SurfaceInterface"][ChainID] = InterfaceSurface
 1871 
 1872         if re.match("^auto$", OptionsInfo["InterfaceSurfaceElectrostatics"], re.I):
 1873             InterfaceSurfaceElectrostatics = True if AminoAcidsPresent else False
 1874         else:
 1875             InterfaceSurfaceElectrostatics = (
 1876                 True if re.match("^yes$", OptionsInfo["InterfaceSurfaceElectrostatics"], re.I) else False
 1877             )
 1878         SpecifiedChainsAndLigandsInfo["SurfaceInterfaceElectrostatics"][ChainID] = InterfaceSurfaceElectrostatics
 1879 
 1880         # A colored surface is always created by default...
 1881         InterfaceSurfaces = True
 1882         SpecifiedChainsAndLigandsInfo["InterfaceSurfaces"][ChainID] = InterfaceSurfaces
 1883 
 1884         if re.match("^auto$", OptionsInfo["InterfaceResidueTypes"], re.I):
 1885             InterfaceResidueTypes = True if AminoAcidsPresent else False
 1886         else:
 1887             InterfaceResidueTypes = True if re.match("^yes$", OptionsInfo["InterfaceResidueTypes"], re.I) else False
 1888         SpecifiedChainsAndLigandsInfo["ResidueTypesInterface"][ChainID] = InterfaceResidueTypes
 1889 
 1890     # Delete loaded object...
 1891     pymol.cmd.delete(MolName)
 1892 
 1893 
 1894 def GetInterfaceResidueTypesStatus(FileIndex, ChainID):
 1895     """Get status of residue types for an interface."""
 1896 
 1897     return OptionsInfo["InfilesInfo"]["SpecifiedChainsAndLigandsInfo"][FileIndex]["ResidueTypesInterface"][ChainID]
 1898 
 1899 
 1900 def GetChainAloneContainsSurfacesStatus(FileIndex, ChainID):
 1901     """Get status of surfaces present in chain alone object."""
 1902 
 1903     return OptionsInfo["InfilesInfo"]["SpecifiedChainsAndLigandsInfo"][FileIndex]["ChainSurfaces"][ChainID]
 1904 
 1905 
 1906 def GetInterfaceContainsSurfacesStatus(FileIndex, ChainID):
 1907     """Get status of surfaces present in an interface."""
 1908 
 1909     return OptionsInfo["InfilesInfo"]["SpecifiedChainsAndLigandsInfo"][FileIndex]["InterfaceSurfaces"][ChainID]
 1910 
 1911 
 1912 def GetChainAloneSurfaceChainStatus(FileIndex, ChainID):
 1913     """Get status of hydrophobic surfaces for chain alone object."""
 1914 
 1915     return OptionsInfo["InfilesInfo"]["SpecifiedChainsAndLigandsInfo"][FileIndex]["SurfaceChain"][ChainID]
 1916 
 1917 
 1918 def GetChainAloneSurfaceChainElectrostaticsStatus(FileIndex, ChainID):
 1919     """Get status of electrostatics surfaces for chain alone object."""
 1920 
 1921     return OptionsInfo["InfilesInfo"]["SpecifiedChainsAndLigandsInfo"][FileIndex]["SurfaceChainElectrostatics"][ChainID]
 1922 
 1923 
 1924 def GetInterfaceSurfaceChainStatus(FileIndex, ChainID):
 1925     """Get status of hydrophobic surfaces for an interface."""
 1926 
 1927     return OptionsInfo["InfilesInfo"]["SpecifiedChainsAndLigandsInfo"][FileIndex]["SurfaceInterface"][ChainID]
 1928 
 1929 
 1930 def GetInterfaceSurfaceChainElectrostaticsStatus(FileIndex, ChainID):
 1931     """Get status of hydrophobic surfaces for an interface."""
 1932 
 1933     return OptionsInfo["InfilesInfo"]["SpecifiedChainsAndLigandsInfo"][FileIndex]["SurfaceInterfaceElectrostatics"][
 1934         ChainID
 1935     ]
 1936 
 1937 
 1938 def CheckPresenceOfValidLigandIDs(ChainsAndLigandsInfo, SpecifiedChainsAndLigandsInfo):
 1939     """Check presence of valid ligand IDs."""
 1940 
 1941     MiscUtil.PrintInfo("\nSpecified chain IDs: %s" % (", ".join(SpecifiedChainsAndLigandsInfo["ChainIDs"])))
 1942 
 1943     for ChainID in SpecifiedChainsAndLigandsInfo["ChainIDs"]:
 1944         if len(SpecifiedChainsAndLigandsInfo["LigandIDs"][ChainID]):
 1945             MiscUtil.PrintInfo(
 1946                 "Chain ID: %s; Specified LigandIDs: %s"
 1947                 % (ChainID, ", ".join(SpecifiedChainsAndLigandsInfo["LigandIDs"][ChainID]))
 1948             )
 1949         else:
 1950             MiscUtil.PrintInfo("Chain IDs: %s; Specified LigandIDs: None" % (ChainID))
 1951             MiscUtil.PrintWarning(
 1952                 "No valid ligand IDs found for chain ID, %s. PyMOL groups and objects related to ligand and binding pockect won't be created."
 1953                 % (ChainID)
 1954             )
 1955 
 1956 
 1957 def RetrieveFirstChainID(FileIndex):
 1958     """Get first chain ID."""
 1959 
 1960     ChainsAndLigandsInfo = OptionsInfo["InfilesInfo"]["ChainsAndLigandsInfo"][FileIndex]
 1961 
 1962     FirstChainID = None
 1963     if len(ChainsAndLigandsInfo["ChainIDs"]):
 1964         FirstChainID = ChainsAndLigandsInfo["ChainIDs"][0]
 1965 
 1966     return FirstChainID
 1967 
 1968 
 1969 def ProcessResidueTypes():
 1970     """Process residue types."""
 1971 
 1972     ResidueTypesNamesInfo, ResidueTypesParamsInfo = PyMOLUtil.ProcessResidueTypesOptionsInfo(
 1973         "-r, --residueTypes", OptionsInfo["ResidueTypes"]
 1974     )
 1975     OptionsInfo["ResidueTypesNames"] = ResidueTypesNamesInfo
 1976     OptionsInfo["ResidueTypesParams"] = ResidueTypesParamsInfo
 1977 
 1978 
 1979 def ProcessSurfaceAtomTypesColors():
 1980     """Process surface atom types colors."""
 1981 
 1982     AtomTypesColorNamesInfo = PyMOLUtil.ProcessSurfaceAtomTypesColorsOptionsInfo(
 1983         "--surfaceAtomTypesColors", OptionsInfo["SurfaceAtomTypesColors"]
 1984     )
 1985     OptionsInfo["AtomTypesColorNames"] = AtomTypesColorNamesInfo
 1986 
 1987 
 1988 def ProcessOptions():
 1989     """Process and validate command line arguments and options."""
 1990 
 1991     MiscUtil.PrintInfo("Processing options...")
 1992 
 1993     # Validate options...
 1994     ValidateOptions()
 1995 
 1996     OptionsInfo["AllowEmptyObjects"] = True if re.match("^Yes$", Options["--allowEmptyObjects"], re.I) else False
 1997 
 1998     OptionsInfo["Infiles"] = Options["--infiles"]
 1999     OptionsInfo["InfilesNames"] = Options["--infileNames"]
 2000 
 2001     OptionsInfo["IgnoreHydrogens"] = True if re.match("^Yes$", Options["--ignoreHydrogens"], re.I) else False
 2002 
 2003     OptionsInfo["Overwrite"] = Options["--overwrite"]
 2004     OptionsInfo["PMLOut"] = True if re.match("^Yes$", Options["--PMLOut"], re.I) else False
 2005 
 2006     OptionsInfo["Outfile"] = Options["--outfile"]
 2007     FileDir, FileName, FileExt = MiscUtil.ParseFileName(OptionsInfo["Outfile"])
 2008     OptionsInfo["PSEOut"] = False
 2009     if re.match("^pml$", FileExt, re.I):
 2010         OptionsInfo["PMLOutfile"] = OptionsInfo["Outfile"]
 2011         OptionsInfo["PMEOutfile"] = re.sub(".pml$", ".pme", OptionsInfo["Outfile"])
 2012     elif re.match("^pse$", FileExt, re.I):
 2013         OptionsInfo["PSEOut"] = True
 2014         OptionsInfo["PSEOutfile"] = OptionsInfo["Outfile"]
 2015         OptionsInfo["PMLOutfile"] = re.sub(".pse$", ".pml", OptionsInfo["Outfile"])
 2016         if os.path.exists(OptionsInfo["PMLOutfile"]) and (not OptionsInfo["Overwrite"]):
 2017             MiscUtil.PrintError(
 2018                 'The intermediate output file to be generated, %s, already exist. Use option "--ov" or "--overwrite" and try again.'
 2019                 % OptionsInfo["PMLOutfile"]
 2020             )
 2021 
 2022     OptionsInfo["InterfaceLabelColor"] = Options["--interfaceLabelColor"]
 2023 
 2024     OptionsInfo["InterfaceContactsCutoff"] = float(Options["--interfaceContactsCutoff"])
 2025 
 2026     OptionsInfo["InterfaceHydrophobicContactsColor"] = Options["--interfaceHydrophobicContactsColor"]
 2027     OptionsInfo["InterfaceHydrophobicContacts"] = (
 2028         True if re.match("^Yes$", Options["--interfaceHydrophobicContacts"], re.I) else False
 2029     )
 2030 
 2031     OptionsInfo["InterfacePolarContactsColor"] = Options["--interfacePolarContactsColor"]
 2032     OptionsInfo["InterfacePolarContacts"] = (
 2033         True if re.match("^Yes$", Options["--interfacePolarContacts"], re.I) else False
 2034     )
 2035 
 2036     OptionsInfo["InterfaceResidueTypes"] = Options["--interfaceResidueTypes"]
 2037     OptionsInfo["InterfaceSurface"] = Options["--interfaceSurface"]
 2038     OptionsInfo["InterfaceSurfaceElectrostatics"] = Options["--interfaceSurfaceElectrostatics"]
 2039 
 2040     OptionsInfo["LabelFontID"] = int(Options["--labelFontID"])
 2041 
 2042     Method = Options["--method"]
 2043     MethodCutoff = Options["--methodCutoff"]
 2044     if re.match("^auto$", MethodCutoff, re.I):
 2045         if re.match("BySASAChange", Method, re.I):
 2046             MethodCutoff = 1.0
 2047         elif re.match("ByHeavyAtomsDistance", Method, re.I):
 2048             MethodCutoff = 5.0
 2049         elif re.match("ByCAlphaAtomsDistance", Method, re.I):
 2050             MethodCutoff = 8.0
 2051         else:
 2052             MiscUtil.PrintError(
 2053                 'The method, %s, specified using "-m, --method" option  is  not a valid method.' % (Method)
 2054             )
 2055     else:
 2056         MethodCutoff = float(Options["--methodCutoff"])
 2057     OptionsInfo["Method"] = Method
 2058     OptionsInfo["MethodCutoff"] = MethodCutoff
 2059 
 2060     OptionsInfo["ResidueTypes"] = Options["--residueTypes"]
 2061     ProcessResidueTypes()
 2062 
 2063     OptionsInfo["SurfaceChain"] = Options["--surfaceChain"]
 2064     OptionsInfo["SurfaceChainElectrostatics"] = Options["--surfaceChainElectrostatics"]
 2065 
 2066     OptionsInfo["SurfaceChainComplex"] = True if re.match("^Yes$", Options["--surfaceChainComplex"], re.I) else False
 2067     OptionsInfo["SurfaceComplex"] = True if re.match("^Yes$", Options["--surfaceComplex"], re.I) else False
 2068 
 2069     # Retrieve surface colors...
 2070     SurfaceColors = re.sub(" ", "", Options["--surfaceColors"])
 2071     SurfaceColorsWords = SurfaceColors.split(",")
 2072     if len(SurfaceColorsWords) != 2:
 2073         MiscUtil.PrintError(
 2074             'The number of comma delinited color names, %d, specified using "--surfaceColors" option, "%s",  must be a 2.'
 2075             % (len(SurfaceColorsWords), Options["--surfaceColors"])
 2076         )
 2077     OptionsInfo["SurfaceColors"] = SurfaceColors
 2078     OptionsInfo["SurfaceInterfaceColor"] = SurfaceColorsWords[0]
 2079     OptionsInfo["SurfaceNonInterfaceColor"] = SurfaceColorsWords[1]
 2080 
 2081     OptionsInfo["SurfaceColorPalette"] = Options["--surfaceColorPalette"]
 2082     OptionsInfo["SurfaceAtomTypesColors"] = Options["--surfaceAtomTypesColors"]
 2083     ProcessSurfaceAtomTypesColors()
 2084 
 2085     OptionsInfo["SurfaceTransparency"] = float(Options["--surfaceTransparency"])
 2086 
 2087     RetrieveInfilesInfo()
 2088 
 2089     # Retrieve interface chain IDs...
 2090     InterfaceChainIDs = re.sub(" ", "", Options["--chainIDs"])
 2091     InterfaceChainIDsList = []
 2092     if not re.match("^auto$", InterfaceChainIDs, re.I):
 2093         InterfaceChainIDsWords = InterfaceChainIDs.split(",")
 2094         if len(InterfaceChainIDsWords) % 2:
 2095             MiscUtil.PrintError(
 2096                 'The number of comma delimited chain IDs, %d, specified using "-c, --chainIDs" option, "%s",  must be a multple of 2.'
 2097                 % (len(InterfaceChainIDsWords), Options["--chainIDs"])
 2098             )
 2099         for ChainID in InterfaceChainIDsWords:
 2100             if not len(ChainID):
 2101                 MiscUtil.PrintError(
 2102                     'A chain ID specified, "%s" using "-c, --chainIDs" option is empty.' % (Options["--chainIDs"])
 2103                 )
 2104             ChainIDWords = ChainID.split("+")
 2105             InterfaceChainIDsList.append(ChainIDWords)
 2106     OptionsInfo["InterfaceChainIDs"] = InterfaceChainIDs
 2107     OptionsInfo["InterfaceChainIDsList"] = InterfaceChainIDsList
 2108 
 2109     # Process interface chain IDs...
 2110     OptionsInfo["LigandIDs"] = Options["--ligandIDs"]
 2111     ProcessInterfaceChainIDs()
 2112 
 2113 
 2114 def RetrieveOptions():
 2115     """Retrieve command line arguments and options."""
 2116 
 2117     # Get options...
 2118     global Options
 2119     Options = docopt(_docoptUsage_)
 2120 
 2121     # Set current working directory to the specified directory...
 2122     WorkingDir = Options["--workingdir"]
 2123     if WorkingDir:
 2124         os.chdir(WorkingDir)
 2125 
 2126     # Handle examples option...
 2127     if "--examples" in Options and Options["--examples"]:
 2128         MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_))
 2129         sys.exit(0)
 2130 
 2131 
 2132 def ValidateOptions():
 2133     """Validate option values."""
 2134 
 2135     MiscUtil.ValidateOptionTextValue("--allowEmptyObjects", Options["--allowEmptyObjects"], "yes no")
 2136 
 2137     # Expand infiles to handle presence of multiple input files...
 2138     InfileNames = MiscUtil.ExpandFileNames(Options["--infiles"], ",")
 2139     InfilesCount = len(InfileNames)
 2140     if not InfilesCount:
 2141         MiscUtil.PrintError('No input files specified for "-i, --infiles" option')
 2142 
 2143     # Validate file extensions...
 2144     for Infile in InfileNames:
 2145         MiscUtil.ValidateOptionFilePath("-i, --infiles", Infile)
 2146         MiscUtil.ValidateOptionFileExt("-i, --infiles", Infile, "pdb cif")
 2147         MiscUtil.ValidateOptionsDistinctFileNames("-i, --infiles", Infile, "-o, --outfile", Options["--outfile"])
 2148 
 2149     # Validate file count...
 2150     if InfilesCount > 2:
 2151         MiscUtil.PrintError(
 2152             'Number of input files, %s, specified using "-i, --infiles" option is not valid. Number of allowed files: 1 or 2'
 2153             % (InfilesCount)
 2154         )
 2155 
 2156     # Validate distinct file names...
 2157     if InfilesCount == 2:
 2158         Infile1Name, Infile2Name = InfileNames
 2159         Infile1Pattern = r"^" + re.escape(Infile1Name) + r"$"
 2160         if re.match(Infile1Pattern, Infile2Name, re.I):
 2161             MiscUtil.PrintError(
 2162                 'The file names specified, "%s", for option "-i, --infiles" must be different.\n'
 2163                 % (Options["--infiles"])
 2164             )
 2165 
 2166     Options["--infileNames"] = InfileNames
 2167 
 2168     MiscUtil.ValidateOptionFileExt("-o, --outfile", Options["--outfile"], "pml pse")
 2169     MiscUtil.ValidateOptionsOutputFileOverwrite(
 2170         "-o, --outfile", Options["--outfile"], "--overwrite", Options["--overwrite"]
 2171     )
 2172 
 2173     MiscUtil.ValidateOptionTextValue("--ignoreHydrogens", Options["--ignoreHydrogens"], "yes no")
 2174 
 2175     MiscUtil.ValidateOptionFloatValue("--interfaceContactsCutoff", Options["--interfaceContactsCutoff"], {">": 0.0})
 2176     MiscUtil.ValidateOptionTextValue(
 2177         "--interfaceHydrophobicContacts", Options["--interfaceHydrophobicContacts"], "yes no"
 2178     )
 2179     MiscUtil.ValidateOptionTextValue("--interfacePolarContacts", Options["--interfacePolarContacts"], "yes no")
 2180 
 2181     MiscUtil.ValidateOptionTextValue("--interfaceResidueTypes", Options["--interfaceResidueTypes"], "yes no auto")
 2182     MiscUtil.ValidateOptionTextValue("--interfaceSurface", Options["--interfaceSurface"], "yes no auto")
 2183     MiscUtil.ValidateOptionTextValue(
 2184         "--interfaceSurfaceElectrostatics", Options["--interfaceSurfaceElectrostatics"], "yes no auto"
 2185     )
 2186 
 2187     MiscUtil.ValidateOptionIntegerValue("--labelFontID", Options["--labelFontID"], {})
 2188 
 2189     MiscUtil.ValidateOptionTextValue(
 2190         "--method", Options["--method"], "BySASAChange ByHeavyAtomsDistance ByCAlphaAtomsDistance"
 2191     )
 2192     if not re.match("^auto$", Options["--methodCutoff"], re.I):
 2193         MiscUtil.ValidateOptionFloatValue("--methodCutoff", Options["--methodCutoff"], {">": 0.0})
 2194 
 2195     MiscUtil.ValidateOptionTextValue("--PMLOut", Options["--PMLOut"], "yes no")
 2196 
 2197     MiscUtil.ValidateOptionTextValue("--surfaceChain", Options["--surfaceChain"], "yes no auto")
 2198     MiscUtil.ValidateOptionTextValue(
 2199         "--surfaceChainElectrostatics", Options["--surfaceChainElectrostatics"], "yes no auto"
 2200     )
 2201     MiscUtil.ValidateOptionTextValue("--surfaceComplex", Options["--surfaceComplex"], "yes no")
 2202     MiscUtil.ValidateOptionTextValue("--surfaceChainComplex", Options["--surfaceChainComplex"], "yes no")
 2203 
 2204     MiscUtil.ValidateOptionTextValue(
 2205         "--surfaceColorPalette", Options["--surfaceColorPalette"], "RedToWhite WhiteToGreen"
 2206     )
 2207     MiscUtil.ValidateOptionFloatValue("--surfaceTransparency", Options["--surfaceTransparency"], {">=": 0.0, "<=": 1.0})
 2208 
 2209 
 2210 # Setup a usage string for docopt...
 2211 _docoptUsage_ = """
 2212 PyMOLVisualizeInterfaces.py - Visualize macromolecular interfaces
 2213 
 2214 Usage:
 2215     PyMOLVisualizeInterfaces.py [--allowEmptyObjects <yes or no>] [--chainIDs <ChainID1 or ChainID1,ChainID2>]
 2216                                               [--interfaceLabelColor <text>] [ --interfaceContactsCutoff <number>]
 2217                                               [--interfaceHydrophobicContacts <yes or no>] [--interfaceHydrophobicContactsColor <text>]
 2218                                               [--interfacePolarContacts <yes or no>] [--interfacePolarContactsColor <text>]
 2219                                               [--interfaceResidueTypes <yes or no>] [--interfaceSurface <yes or no>]
 2220                                               [--interfaceSurfaceElectrostatics <yes or no>] [--labelFontID <number>]
 2221                                               [--ignoreHydrogens <yes or no>] [--ligandIDs <Largest, All, None or ID1,ID2...>]
 2222                                               [--method <text>] [--methodCutoff <number>]
 2223                                               [--PMLOut <yes or no>] [--residueTypes <Type,Color,ResNames,...>] [--surfaceChain <yes or no>]
 2224                                               [--surfaceChainElectrostatics <yes or no>] [--surfaceChainComplex <yes or no>]
 2225                                               [--surfaceComplex <yes or no>] [--surfaceColors <ColorName1,ColorName2>]
 2226                                               [--surfaceColorPalette <RedToWhite or WhiteToGreen>]
 2227                                               [--surfaceAtomTypesColors <ColorType,ColorSpec,...>] [--surfaceTransparency <number>]
 2228                                               [--overwrite] [-w <dir>] -i <infile1,...> -o <outfile>
 2229     PyMOLVisualizeInterfaces.py -h | --help | -e | --examples
 2230 
 2231 Description:
 2232     Generate PyMOL visualization files for viewing interfaces between macromolecules
 2233     including proteins and nucleic acids. The interfaces may be generated between
 2234     pairs of chains in a single file or across two different files.
 2235 
 2236     The supported input file format are: PDB (.pdb), CIF (.cif)
 2237 
 2238     The supported output file formats are: PyMOL script file (.pml), PyMOL session
 2239     file (.pse)
 2240 
 2241     A variety of PyMOL groups and objects may be  created for visualization of
 2242     macromolecular interfaces. These groups and objects correspond to complexes,
 2243     surfaces, chains, ligands, and interfaces. A complete hierarchy of all possible
 2244     PyMOL groups and objects is shown below:
 2245 
 2246         <PDBFileRoot>
 2247             .Complex
 2248                 .Complex
 2249                 .Surface
 2250             .Chain<ID>
 2251                 .Complex
 2252                     .Complex
 2253                     .Surface
 2254                 .Chain
 2255                     .Chain
 2256                     .NonInterface
 2257                         .Chain
 2258                         .Surface
 2259                             .Surface
 2260                             .Hydrophobicity
 2261                             .Hydrophobicity_Charge
 2262                             .Vacuum_Electrostatics
 2263                                 .Contact_Potentials
 2264                                 .Map
 2265                                 .Legend
 2266                                 .Volume
 2267                 .Solvent
 2268                 .Inorganic
 2269                 .Ligand<ID>
 2270                     .Ligand
 2271                         .Ligand
 2272                         .BallAndStick
 2273                 .Ligand<ID>
 2274                     .Ligand
 2275                         ... ... ...
 2276             .Chain<ID>
 2277                 ... ... ...
 2278                 .Ligand<ID>
 2279                     ... ... ...
 2280                 .Ligand<ID>
 2281                     ... ... ...
 2282             .Chain<ID>
 2283                 ... ... ...
 2284         <PDBFileRoot>
 2285             .Complex
 2286                 ... ... ...
 2287             .Chain<ID>
 2288                 ... ... ...
 2289                 .Ligand<ID>
 2290                     ... ... ...
 2291                 .Ligand<ID>
 2292                     ... ... ...
 2293             .Chain<ID>
 2294                 ... ... ...
 2295         <Interfaces>
 2296             .Chain<IDs1>_Chain<IDs2>
 2297                 .Polar_Contacts
 2298                 .Hydrophobic_Contacts
 2299                 .Chain<ID> or Chain<ID>_<PDBFileRoot>
 2300                     .Chain
 2301                     .Residues
 2302                         .Aromatic
 2303                             .Residues
 2304                             .Surface
 2305                         .Hydrophobic
 2306                             .Residues
 2307                             .Surface
 2308                         .Polar
 2309                             .Residues
 2310                             .Surface
 2311                         .Positively_Charged
 2312                             .Residues
 2313                             .Surface
 2314                         .Negatively_Charged
 2315                             .Residues
 2316                             .Surface
 2317                         .Other
 2318                             .Residues
 2319                             .Surface
 2320                     .Surface
 2321                         .Surface
 2322                         .Hydrophobicity
 2323                         .Hydrophobicity_Charge
 2324                         .Vacuum_Electrostatics
 2325                             .Contact_Potentials
 2326                             .Map
 2327                             .Legend
 2328                             .Volume
 2329                 .Chain<ID> or <PDBFileRoot>_Chain<ID>
 2330                     .Chain
 2331                     .Residues
 2332                         ... ... ...
 2333                     .Surface
 2334                         ... ... ...
 2335             .Chain<IDs>_Chain<IDs>
 2336                 .Polar_Contacts
 2337                 .Hydrophobic_Contacts
 2338                 .Chain<ID> or Chain<ID>_<PDBFileRoot>
 2339                     .Chain
 2340                     .Residues
 2341                         ... ... ...
 2342                     .Surface
 2343                         ... ... ...
 2344                 .Chain<ID> or Chain<ID>_<PDBFileRoot>
 2345                     .Chain
 2346                     .Residues
 2347                         ... ... ...
 2348                     .Surface
 2349                         ... ... ...
 2350     
 2351     The hydrophobic and electrostatic surfaces are not created for complete complex
 2352     and chain complex in input file(s) by default. A word to the wise: The creation of
 2353     surface objects may slow down loading of PML file and generation of PSE file, based
 2354     on the size of input complexes. The generation of PSE file may also fail.
 2355 
 2356 Options:
 2357     --allowEmptyObjects <yes or no>  [default: no]
 2358         Allow creation of empty PyMOL objects corresponding to interface,
 2359         solvent, and inorganic atom selections across chains and ligands in
 2360         input file(s). By default, the empty objects are marked for deletion.
 2361     -c, --chainIDs <ChainID1,ChainD2,...>  [default: Auto]
 2362         Pairwise comma delimited list of chain IDs for the identification of
 2363         macromolecular interfaces. All chain IDs must be present in the
 2364         same file for a single input file. Otherwise, the first and second
 2365         chain ID(s) in a pair belong to the first and second input file.
 2366         
 2367         The default values for interface chain IDs depend on the number
 2368         of input files as shown below:
 2369         
 2370         One input file: First two chains
 2371         Two input files: First chain in each input file
 2372         
 2373         Each chain may contain multiple chain IDs delimited by a plus sign. For
 2374         example, A+B,C+D chain pair specifies interface between chain complexes
 2375         A+B and C+D in first input file or across two input files.
 2376     -e, --examples
 2377         Print examples.
 2378     -h, --help
 2379         Print this help message.
 2380     -i, --infiles <infile or infile1,infile2>
 2381         Name of an input file or a comma delmited list of names for two input
 2382         files.
 2383     --ignoreHydrogens <yes or no>  [default: yes]
 2384         Ignore hydrogens for ligand views.
 2385     --interfaceLabelColor <text>  [default: magenta]
 2386         Color for drawing residue or atom level labels for residues in an interface.
 2387         The specified value must be valid color. No validation is performed.
 2388     --interfaceContactsCutoff <number>  [default: 4.0]
 2389         Distance in Angstroms for identifying polar and hyrdophobic contacts
 2390         between atoms in interface reisudes.
 2391     --interfaceHydrophobicContacts <yes or no>  [default: yes]
 2392         Hydrophobic contacts between residues in an interface. The hydrophobic
 2393         contacts are shown between pairs of carbon atoms not connected to
 2394         hydrogen bond donor or acceptors atoms as identified by PyMOL.
 2395     --interfaceHydrophobicContactsColor <text>  [default: purpleblue]
 2396         Color for drawing hydrophobic contacts between residues in an interface.
 2397         The specified value must be valid color. No validation is performed.
 2398     --interfacePolarContacts <yes or no>  [default: yes]
 2399         Polar contacts between residues in an interface.
 2400     --interfacePolarContactsColor <text>  [default: orange]
 2401         Color for drawing polar contacts between residues in an interface.
 2402         The specified value must be valid color. No validation is performed.
 2403     --interfaceResidueTypes <yes or no>  [default: auto]
 2404         Interface residue types. The residue groups are generated using residue types,
 2405         colors, and names specified by '--residueTypes' option. It is only valid for
 2406         amino acids.  By default, the residue type groups are automatically created
 2407         for interfaces containing amino acids and skipped for chains only containing
 2408         nucleic acids.
 2409     --interfaceSurface <yes or no>  [default: auto]
 2410         Surfaces around interface residues colored by hydrophobicity alone and
 2411         both hydrophobicity and charge. The hydrophobicity surface is colored
 2412         at residue level using Eisenberg hydrophobicity scale for residues and color
 2413         gradient specified by '--surfaceColorPalette' option. The  hydrophobicity and
 2414         charge surface is colored [ Ref 140 ] at atom level using colors specified for
 2415         groups of atoms by '--surfaceAtomTypesColors' option. This scheme allows
 2416         simultaneous mapping of hyrophobicity and charge values on the surfaces.
 2417         
 2418         This option is only valid for amino acids. By default, both surfaces are
 2419         automatically created for pockets containing amino acids and skipped for
 2420         pockets containing only nucleic acids.
 2421         
 2422         In addition, generic surfaces colored by '--surfaceColors' are always created
 2423         for interface residues containing amino acids and nucleic acids.
 2424     --interfaceSurfaceElectrostatics <yes or no>  [default: no]
 2425         Vacuum electrostatics contact potential surface around interface residues.
 2426         A word to the wise from PyMOL documentation: The computed protein
 2427         contact potentials are only qualitatively useful, due to short cutoffs,
 2428         truncation, and lack of solvent "screening".
 2429         
 2430         This option is only valid for amino acids. By default, the electrostatics surface
 2431         is automatically created for chains containing amino acids and skipped for chains
 2432         containing only nucleic acids.
 2433     --labelFontID <number>  [default: 7]
 2434         Font ID for drawing labels. Default: 7 (Sans Bold). Valid values: 5 to 16.
 2435         The specified value must be a valid PyMOL font ID. No validation is
 2436         performed. The complete lists of valid font IDs is available at:
 2437         pymolwiki.org/index.php/Label_font_id. Examples: 5 - Sans;
 2438         7 - Sans Bold; 9 - Serif; 10 - Serif Bold.
 2439     -l, --ligandIDs <Largest, All, None or ID1,ID2...>  [default: All]
 2440         List of ligand IDs to show in chains during visualization of interfaces. Possible
 2441         values: Largest, All, None, or a comma delimited list of ligand IDs. The
 2442         default is to show all ligands present in chains involved in interfaces.
 2443         
 2444         Ligands are identified using organic selection operator available in PyMOL.
 2445         It'll also  identify buffer molecules as ligands. The largest ligand contains
 2446         the highest number of heavy atoms.
 2447     -m, --method <text>  [default: BySASAChange]
 2448         Methodology for the identification of interface residues between a pair
 2449         of chains in an input file. The interface residues may be identified by
 2450         change in solvent accessible surface area (SASA) for a residue between
 2451         a chain and chains complex, distance between heavy atoms
 2452         in two chains, or distance between CAlpha atoms. Possible values:
 2453         BySASAChange, ByHeavyAtomsDistance, or ByCAlphaAtomsDistance. 
 2454     --methodCutoff <number>  [default: auto]
 2455         Cutoff value used by different methodologies during the identification of
 2456         interface residues between a pair of chains. The default values are
 2457         shown below:
 2458         
 2459             BySASAChange: 1.0; Units: Angstrom**2 [ Ref 141 ]
 2460             ByHeavyAtomsDistance: 5.0; Units: Angstrom [ Ref 142 ]
 2461             ByCAlphaAtomsDistance: 8.0; Units: Angstrom [ Ref 143 ]
 2462         
 2463     -o, --outfile <outfile>
 2464         Output file name.
 2465     -p, --PMLOut <yes or no>  [default: yes]
 2466         Save PML file during generation of PSE file.
 2467     -r, --residueTypes <Type,Color,ResNames,...>  [default: auto]
 2468         Residue types, colors, and names to generate for residue groups during
 2469         and '--residueTypesChain' option. It is only valid for amino acids.
 2470         
 2471         It is a triplet of comma delimited list of amino acid residues type, residues
 2472         color, and a space delimited list three letter residue names. 
 2473         
 2474         The default values for residue type, color, and name triplets  are shown
 2475         below:
 2476             
 2477             Aromatic,brightorange,HIS PHE TRP TYR,
 2478             Hydrophobic,orange,ALA GLY VAL LEU ILE PRO MET,
 2479             Polar,palegreen,ASN GLN SER THR CYS,
 2480             Positively_Charged,marine,ARG LYS,
 2481             Negatively_Charged,red,ASP GLU
 2482             
 2483         The color name must be a valid PyMOL name. No validation is performed.
 2484         An amino acid name may appear across multiple residue types. All other
 2485         residues are grouped under 'Other'.
 2486     --surfaceChain <yes or no>  [default: auto]
 2487         Surfaces around non-interface residues in individual  chain colored by
 2488         hydrophobicity alone and both hydrophobicity and charge. The hydrophobicity
 2489         surface is colored at residue level using Eisenberg hydrophobicity scale for residues
 2490         and color gradient specified by '--surfaceColorPalette' option. The  hydrophobicity
 2491         and charge surface is colored [ Ref 140 ] at atom level using colors specified for
 2492         groups of atoms by '--surfaceAtomTypesColors' option. This scheme allows
 2493         simultaneous mapping of hyrophobicity and charge values on the surfaces.
 2494         
 2495         This option is only valid for amino acids. By default, both surfaces are
 2496         automatically created for chains containing amino acids and skipped for
 2497         chains containing only nucleic acids.
 2498         
 2499         In addition, generic surfaces colored by '--surfaceColors' are always created
 2500         for non-interface residues containing amino acids and nucleic acids.
 2501     --surfaceChainElectrostatics <yes or no>  [default: no]
 2502         Vacuum electrostatics contact potential surface and volume around non-interface
 2503         residues in individual chain. A word to the wise from PyMOL documentation: The
 2504         computed protein contact potentials are only qualitatively useful, due to short cutoffs,
 2505         truncation, and lack of solvent "screening".
 2506         
 2507         This option is only valid for amino acids. By default, the electrostatics surface
 2508         and volume are automatically created for chains containing amino acids and
 2509         skipped for chains containing only nucleic acids.
 2510     --surfaceChainComplex <yes or no>  [default: no]
 2511         Hydrophobic surface around chain complex. The  surface is colored by
 2512         hydrophobicity. It is only valid for amino acids.
 2513     --surfaceComplex <yes or no>  [default: no]
 2514         Hydrophobic surface around complete complex. The  surface is colored by
 2515         hydrophobicity. It is only valid for amino acids.
 2516     --surfaceColors <ColorName1,ColorName2>  [default: salmon,lightblue]
 2517         Color names for surfaces around interface residues and non-interface
 2518         residues in chains. These colors are not used for surfaces colored by
 2519         hydrophobicity and charge. The color names must be valid PyMOL names.
 2520     --surfaceColorPalette <RedToWhite or WhiteToGreen>  [default: RedToWhite]
 2521         Color palette for hydrophobic surfaces around chains and interface residues
 2522         in proteins. Possible values: RedToWhite or WhiteToGreen from most
 2523         hydrophobic amino acid to least hydrophobic. The colors values for amino
 2524         acids are taken from color_h script available as part of the Script Library at
 2525         PyMOL Wiki.
 2526     --surfaceAtomTypesColors <ColorType,ColorSpec,...>  [default: auto]
 2527         Atom colors for generating surfaces colored by hyrophobicity and charge
 2528         around chains and interface residues in proteins. It's a pairwise comma
 2529         delimited list of atom color type and color specification for goups of atoms.
 2530         
 2531         The default values for color types [ Ref 140 ] along wth color specifications
 2532         are shown below: 
 2533             
 2534             HydrophobicAtomsColor, yellow,
 2535             NegativelyChargedAtomsColor, red,
 2536             PositivelyChargedAtomsColor, blue,
 2537             OtherAtomsColor, gray90
 2538             
 2539         The color names must be valid PyMOL names.
 2540         
 2541         The color values may also be specified as space delimited RGB triplets:
 2542              
 2543             HydrophobicAtomsColor, 0.95 0.78 0.0,
 2544             NegativelyChargedAtomsColor, 1.0 0.4 0.4,
 2545             PositivelyChargedAtomsColor, 0.2 0.5 0.8,
 2546             OtherAtomsColor, 0.95 0.95 0.95
 2547             
 2548     --surfaceTransparency <number>  [default: 0.25]
 2549         Surface transparency for molecular surfaces.
 2550     --overwrite
 2551         Overwrite existing files.
 2552     -w, --workingdir <dir>
 2553         Location of working directory which defaults to the current directory.
 2554 
 2555 Examples:
 2556     To visualize interface residues between the first two chains in a PDB file,
 2557     using default methodology to identify interfaces, and and generate a PML
 2558     file, type: 
 2559 
 2560         % PyMOLVisualizeInterfaces.py -i Sample8.pdb -o Sample8.pml
 2561 
 2562     To visualize interface residues between a pair of specific chains in a PDB
 2563     file using a specific methodology and cutoff value to identify interfaces, and
 2564     generate a PML file, type: 
 2565 
 2566         % PyMOLVisualizeInterfaces.py -m BySASAChange --methodCutoff 1.0
 2567         -c "A,B" -i Sample8.pdb -o Sample8.pml
 2568 
 2569     To visualize interface residues between multiple pairs of specified chains in
 2570     a PDB file using a specific methodology and cutoff value to identify interfaces,
 2571     and generate a PML file, type: 
 2572 
 2573         % PyMOLVisualizeInterfaces.py -m ByHeavyAtomsDistance
 2574         --methodCutoff 5.0 -c "A,B,B,D" -i Sample8.pdb -o Sample8.pml
 2575 
 2576     To visualize interface residues between a pair of specified chains, each member
 2577     containing multiple chains, a PDB file using a specific methodology and cutoff
 2578     value to identify interfaces, and generate a PML file, type: 
 2579 
 2580         % PyMOLVisualizeInterfaces.py -m ByCAlphaAtomsDistance
 2581         --methodCutoff 8.0 -c "A+C,B+D" -i Sample8.pdb -o Sample8.pml
 2582 
 2583     To visualize interface residues between a pair of specific chains across two PDB
 2584     files using a specific methodology and cutoff value to identify interfaces, and
 2585     generate a PML file, type: 
 2586 
 2587         % PyMOLVisualizeInterfaces.py -m BySASAChange --methodCutoff 1.0 
 2588         -c "A,B" -i Sample8Part1.pdb,Sample8Part2.pdb
 2589         -o Sample8.pml
 2590 
 2591     To visualize interface residues between multiple pairs of specified chains across
 2592     two PDB files using a specific methodology and cutoff value to identify interfaces,
 2593     and generate a PML file, type: 
 2594 
 2595         % PyMOLVisualizeInterfaces.py -m ByHeavyAtomsDistance
 2596         --methodCutoff 5.0  -c "A,B,C,B" -i Sample8Part1.pdb,Sample8Part2.pdb
 2597         -o Sample8.pml
 2598 
 2599     To visualize interface residues between a pair of specified chains, each member
 2600     containing multiple chains, across two PDB files using a specific methodology
 2601     and cutoff value to identify interfaces, and generate a PML file, type:
 2602 
 2603         % PyMOLVisualizeInterfaces.py -m ByCAlphaAtomsDistance
 2604         --methodCutoff 8.0  -c "A+C,B+D" -i "Sample8Part1.pdb,Sample8Part2.pdb"
 2605         -o Sample8.pml
 2606 
 2607 Author:
 2608     Manish Sud(msud@san.rr.com)
 2609 
 2610 See also:
 2611     DownloadPDBFiles.pl,  PyMOLVisualizeCryoEMDensity.py,
 2612     PyMOLVisualizeElectronDensity.py, PyMOLVisualizeMacromolecules.py,
 2613     PyMOLVisualizeSurfaceAndBuriedResidues.py
 2614 
 2615 Copyright:
 2616     Copyright (C) 2025 Manish Sud. All rights reserved.
 2617 
 2618     The functionality available in this script is implemented using PyMOL, a
 2619     molecular visualization system on an open source foundation originally
 2620     developed by Warren DeLano.
 2621 
 2622     This file is part of MayaChemTools.
 2623 
 2624     MayaChemTools is free software; you can redistribute it and/or modify it under
 2625     the terms of the GNU Lesser General Public License as published by the Free
 2626     Software Foundation; either version 3 of the License, or (at your option) any
 2627     later version.
 2628 
 2629 """
 2630 
 2631 if __name__ == "__main__":
 2632     main()