1 #!/bin/env python 2 # 3 # File: PyMOLCalculateRMSD.py 4 # Author: Manish Sud <msud@san.rr.com> 5 # 6 # Copyright (C) 2024 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 # Add local python path to the global path and import standard library modules... 33 import os 34 import sys; sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), "..", "lib", "Python")) 35 import time 36 import re 37 38 # PyMOL imports... 39 try: 40 import pymol 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 try: 52 from docopt import docopt 53 import MiscUtil 54 import PyMOLUtil 55 except ImportError as ErrMsg: 56 sys.stderr.write("\nFailed to import MayaChemTools module/package: %s\n" % ErrMsg) 57 sys.stderr.write("Check/update your MayaChemTools environment and try again.\n\n") 58 sys.exit(1) 59 60 ScriptName = os.path.basename(sys.argv[0]) 61 Options = {} 62 OptionsInfo = {} 63 64 def main(): 65 """Start execution of the script.""" 66 67 MiscUtil.PrintInfo("\n%s (PyMOL v%s; MayaChemTools v%s; %s): Starting...\n" % (ScriptName, pymol.cmd.get_version()[0], MiscUtil.GetMayaChemToolsVersion(), time.asctime())) 68 69 (WallClockTime, ProcessorTime) = MiscUtil.GetWallClockAndProcessorTime() 70 71 # Retrieve command line arguments and options... 72 RetrieveOptions() 73 74 # Process and validate command line arguments and options... 75 ProcessOptions() 76 77 # Perform actions required by the script... 78 CalculateRMSDValues() 79 80 MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName) 81 MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime)) 82 83 def CalculateRMSDValues(): 84 """Calculate RMSD between reference and probe files.""" 85 86 Outfile = OptionsInfo["Outfile"] 87 OutDelim = OptionsInfo["OutDelim"] 88 89 MiscUtil.PrintInfo("\nGenerating file %s..." % Outfile) 90 OutFH = open(Outfile, "w") 91 if OutFH is None: 92 MiscUtil.PrintError("Couldn't open output file: %s.\n" % (Outfile)) 93 94 WriteColumnLabels(OutFH, OutDelim) 95 96 pymol.cmd.reinitialize() 97 if re.match("^OneToOne$", OptionsInfo["Mode"], re.I): 98 CalculateOneToOneRMSDValues(OutFH, OutDelim) 99 elif re.match("^AllToAll$", OptionsInfo["Mode"], re.I): 100 CalculateAllToAllRMSDValues(OutFH, OutDelim) 101 elif re.match("^FirstToAll$", OptionsInfo["Mode"], re.I): 102 CalculateFirstToAllRMSDValues(OutFH, OutDelim) 103 else: 104 MiscUtil.PrintError("RMSD couldn't be calculated: Specified mode, %s, is not supported" % OptionsInfo["Mode"]) 105 106 OutFH.close() 107 108 def CalculateOneToOneRMSDValues(OutFH, OutDelim): 109 """Calculate pairwise RMSD values.""" 110 111 RefFilesCount = len(OptionsInfo["RefFilesNames"]) 112 ProbeFilesCount = len(OptionsInfo["ProbeFilesNames"]) 113 114 FilesCount = ProbeFilesCount if RefFilesCount > ProbeFilesCount else RefFilesCount 115 116 if RefFilesCount != ProbeFilesCount: 117 MiscUtil.PrintWarning("Number of reference files, %d, is not equal to number of probe files, %d .\n" % (RefFilesCount, ProbeFilesCount)) 118 MiscUtil.PrintWarning("Pairwise RMSD will be calculated only for first %s files.\n" % (FilesCount)) 119 120 # Process files... 121 for FileIndex in range(0, FilesCount): 122 RefFileIndex = FileIndex 123 ProbeFileIndex = FileIndex 124 125 LoadRefFile(RefFileIndex) 126 LoadProbeFile(ProbeFileIndex) 127 128 RMSD = CalculateRMSDValue(RefFileIndex, ProbeFileIndex) 129 130 RefID = OptionsInfo["RefFilesInfo"]["FilesRoots"][RefFileIndex] 131 ProbeID = OptionsInfo["ProbeFilesInfo"]["FilesRoots"][ProbeFileIndex] 132 Line = "%s%s%s%s%s\n" % (RefID, OutDelim, ProbeID, OutDelim, RMSD) 133 OutFH.write(Line) 134 135 DeleteRefObject(RefFileIndex) 136 DeleteProbeObject(ProbeFileIndex) 137 138 def CalculateAllToAllRMSDValues(OutFH, OutDelim): 139 """Calculate RMSD values between all pairs of files.""" 140 141 RefFilesCount = len(OptionsInfo["RefFilesNames"]) 142 ProbeFilesCount = len(OptionsInfo["ProbeFilesNames"]) 143 OutMatrix = OptionsInfo["OutMatrix"] 144 145 for RefFileIndex in range(0, RefFilesCount): 146 LoadRefFile(RefFileIndex) 147 RefID = OptionsInfo["RefFilesInfo"]["FilesRoots"][RefFileIndex] 148 149 LineWords = [] 150 if OutMatrix: 151 LineWords.append(RefID) 152 153 for ProbeFileIndex in range(0, ProbeFilesCount): 154 LoadProbeFile(ProbeFileIndex) 155 RMSD = CalculateRMSDValue(RefFileIndex, ProbeFileIndex) 156 DeleteProbeObject(ProbeFileIndex) 157 158 if OutMatrix: 159 LineWords.append(RMSD) 160 else: 161 ProbeID = OptionsInfo["ProbeFilesInfo"]["FilesRoots"][ProbeFileIndex] 162 Line = "%s%s%s%s%s\n" % (RefID, OutDelim, ProbeID, OutDelim, RMSD) 163 OutFH.write(Line) 164 165 DeleteRefObject(RefFileIndex) 166 167 if OutMatrix: 168 Line = OutDelim.join(LineWords) 169 OutFH.write("%s\n" % Line) 170 171 def CalculateFirstToAllRMSDValues(OutFH, OutDelim): 172 """Calculate RMSD values between first reference file and all probe files.""" 173 174 # Setup reference... 175 RefFileIndex = 0 176 RefID = OptionsInfo["RefFilesInfo"]["FilesRoots"][RefFileIndex] 177 LoadRefFile(RefFileIndex) 178 179 # Go over probe files... 180 for ProbeFileIndex in range(0, len(OptionsInfo["ProbeFilesNames"])): 181 LoadProbeFile(ProbeFileIndex) 182 183 RMSD = CalculateRMSDValue(RefFileIndex, ProbeFileIndex) 184 185 ProbeID = OptionsInfo["ProbeFilesInfo"]["FilesRoots"][ProbeFileIndex] 186 Line = "%s%s%s%s%s\n" % (RefID, OutDelim, ProbeID, OutDelim, RMSD) 187 OutFH.write(Line) 188 189 DeleteProbeObject(ProbeFileIndex) 190 191 DeleteRefObject(RefFileIndex) 192 193 def WriteColumnLabels(OutFH, OutDelim): 194 """Write out column labels.""" 195 196 ColLabels = [] 197 198 if re.match("^AllToAll$", OptionsInfo["Mode"], re.I) and OptionsInfo["OutMatrix"]: 199 ColLabels.append("") 200 ColLabels.extend(OptionsInfo["ProbeFilesInfo"]["FilesRoots"]) 201 else: 202 ColLabels = ["RefFileID", "ProbeFileID", "RMSD"] 203 204 Line = OutDelim.join(ColLabels) 205 OutFH.write("%s\n" % Line) 206 207 def LoadRefFile(RefFileIndex): 208 """Load reference file.""" 209 210 RefFile = OptionsInfo["RefFilesNames"][RefFileIndex] 211 RefName = OptionsInfo["RefFilesInfo"]["PyMOLObjectNames"][RefFileIndex] 212 LoadFile(RefFile, RefName) 213 214 def LoadProbeFile(ProbeFileIndex): 215 """Load probe file.""" 216 217 ProbeFile = OptionsInfo["ProbeFilesNames"][ProbeFileIndex] 218 ProbeName = OptionsInfo["ProbeFilesInfo"]["PyMOLObjectNames"][ProbeFileIndex] 219 LoadFile(ProbeFile, ProbeName) 220 221 def LoadFile(FileName, ObjectName): 222 """Load a file.""" 223 224 pymol.cmd.load(FileName, ObjectName) 225 226 def DeleteRefObject(RefFileIndex): 227 """Delete reference object.""" 228 229 RefName = OptionsInfo["RefFilesInfo"]["PyMOLObjectNames"][RefFileIndex] 230 DeleteObject(RefName) 231 232 def DeleteProbeObject(ProbeFileIndex): 233 """Delete probe object.""" 234 235 ProbeName = OptionsInfo["ProbeFilesInfo"]["PyMOLObjectNames"][ProbeFileIndex] 236 DeleteObject(ProbeName) 237 238 def DeleteObject(Name): 239 """Delete PyMOL object.""" 240 241 pymol.cmd.delete(Name) 242 243 def CalculateRMSDValue(RefFileIndex, ProbeFileIndex): 244 """Calculate RMSD value between referece and probe objects.""" 245 246 RefName = OptionsInfo["RefFilesInfo"]["PyMOLObjectNames"][RefFileIndex] 247 ProbeName = OptionsInfo["ProbeFilesInfo"]["PyMOLObjectNames"][ProbeFileIndex] 248 249 if re.match("^FirstChain$", OptionsInfo["AlignMode"], re.I): 250 RefFirstChainID = OptionsInfo["RefFilesInfo"]["ChainIDs"][RefFileIndex][0] 251 RefSelection = "(%s and chain %s)" % (RefName, RefFirstChainID) 252 253 ProbeFirstChainID = OptionsInfo["ProbeFilesInfo"]["ChainIDs"][ProbeFileIndex][0] 254 ProbeSelection = "(%s and chain %s)" % (ProbeName, ProbeFirstChainID) 255 else: 256 RefSelection = RefName 257 ProbeSelection = ProbeName 258 259 RMSD = CalculateRMSD(RefSelection, ProbeSelection, OptionsInfo["AlignMethod"]) 260 261 return RMSD 262 263 def CalculateRMSD(RefSelectionName, ProbeSelectionName, AlignMethod): 264 """Calculate RMSD between two selections after aligning the selections.""" 265 266 if re.match("^align$", AlignMethod, re.I): 267 Results = pymol.cmd.align(ProbeSelectionName, RefSelectionName) 268 RMSD = Results[0] 269 elif re.match("^cealign$", AlignMethod, re.I): 270 Results = pymol.cmd.cealign(RefSelectionName, ProbeSelectionName) 271 RMSD = Results["RMSD"] 272 elif re.match("^super$", AlignMethod, re.I): 273 Results = pymol.cmd.super(ProbeSelectionName, RefSelectionName) 274 RMSD = Results[0] 275 else: 276 RMSD = None 277 MiscUtil.PrintWarning("Failed to calculate RMSD. Unknown alignment method: %s" % AlignMethod) 278 279 if RMSD is not None: 280 RMSD = "%.2f" % RMSD 281 282 return RMSD 283 284 def RetrieveProbeFilesInfo(): 285 """Retrieve information for probe input files.""" 286 287 RetrieveInfilesInfo("ProbeFiles") 288 289 def RetrieveRefFilesInfo(): 290 """Retrieve information for reference input files.""" 291 292 RetrieveInfilesInfo("RefFiles") 293 294 def RetrieveInfilesInfo(InfilesMode): 295 """Retrieve information for input files.""" 296 297 if re.match("^ProbeFiles$", InfilesMode, re.I): 298 MiscUtil.PrintInfo("Retrieving information for probe files...") 299 InfilesNames = OptionsInfo["ProbeFilesNames"] 300 NameSuffix = "_Probe" 301 elif re.match("^RefFiles$", InfilesMode, re.I): 302 MiscUtil.PrintInfo("Retrieving information for reference files...") 303 InfilesNames = OptionsInfo["RefFilesNames"] 304 NameSuffix = "_Ref" 305 else: 306 MiscUtil.PrintError("Internal Error: Unknown infiles mode: %s" % InfilesMode) 307 308 InfilesInfo = {} 309 310 InfilesInfo["FilesNames"] = [] 311 InfilesInfo["FilesRoots"] = [] 312 InfilesInfo["ChainIDs"] = [] 313 InfilesInfo["PyMOLObjectNames"] = [] 314 315 for Infile in InfilesNames: 316 MiscUtil.PrintInfo("\nRetrieving chains information for input file %s..." % Infile) 317 318 FileDir, FileName, FileExt = MiscUtil.ParseFileName(Infile) 319 InfileRoot = FileName 320 321 ChainIDs = RetrieveChainIDs(Infile, InfileRoot) 322 if not len(ChainIDs): 323 if re.match("^FirstChain$", OptionsInfo["AlignMode"], re.I): 324 MiscUtil.PrintError("The align mode, %s, can't be used for calculating RMSD: No non-empty chain IDs found in input file." % (OptionsInfo["AlignMode"])) 325 326 InfilesInfo["FilesNames"].append(Infile) 327 InfilesInfo["FilesRoots"].append(InfileRoot) 328 InfilesInfo["ChainIDs"].append(ChainIDs) 329 330 Name = "%s%s" % (InfileRoot, NameSuffix) 331 InfilesInfo["PyMOLObjectNames"].append(Name) 332 333 if re.match("^ProbeFiles$", InfilesMode, re.I): 334 OptionsInfo["ProbeFilesInfo"] = InfilesInfo 335 elif re.match("^RefFiles$", InfilesMode, re.I): 336 OptionsInfo["RefFilesInfo"] = InfilesInfo 337 338 def RetrieveChainIDs(Infile, InfileRoot): 339 """Retrieve chains IDs for an input file.""" 340 341 pymol.cmd.reinitialize() 342 343 MolName = InfileRoot 344 pymol.cmd.load(Infile, MolName) 345 346 ChainIDs = PyMOLUtil.GetChains(MolName, RemoveEmpty = True) 347 pymol.cmd.delete(MolName) 348 349 if ChainIDs is None: 350 ChainIDs = [] 351 352 # Print out chain and ligand IDs... 353 ChainInfo = ", ".join(ChainIDs) if len(ChainIDs) else "None" 354 MiscUtil.PrintInfo("Chain IDs: %s" % ChainInfo) 355 356 return ChainIDs 357 358 def ProcessOptions(): 359 """Process and validate command line arguments and options.""" 360 361 MiscUtil.PrintInfo("Processing options...") 362 363 # Validate options... 364 ValidateOptions() 365 366 OptionsInfo["AlignMethod"] = Options["--alignMethod"].lower() 367 OptionsInfo["AlignMode"] = Options["--alignMode"] 368 369 OptionsInfo["Mode"] = Options["--mode"] 370 371 OptionsInfo["ProbeFiles"] = Options["--probefiles"] 372 OptionsInfo["ProbeFilesNames"] = Options["--probeFilesNames"] 373 374 OptionsInfo["RefFiles"] = Options["--reffiles"] 375 OptionsInfo["RefFilesNames"] = Options["--refFilesNames"] 376 377 RetrieveProbeFilesInfo() 378 RetrieveRefFilesInfo() 379 380 OptionsInfo["Outfile"] = Options["--outfile"] 381 OptionsInfo["OutMatrix"] = True if re.match("^Yes$", Options["--outMatrix"], re.I) else False 382 383 OptionsInfo["Overwrite"] = Options["--overwrite"] 384 385 OptionsInfo["OutDelim"] = " " 386 if MiscUtil.CheckFileExt(OptionsInfo["Outfile"], "csv"): 387 OptionsInfo["OutDelim"] = "," 388 elif MiscUtil.CheckFileExt(OptionsInfo["Outfile"], "tsv txt"): 389 OptionsInfo["OutDelim"] = "\t" 390 else: 391 MiscUtil.PrintError("The file name specified , %s, for option \"--outfile\" is not valid. Supported file formats: csv tsv txt\n" % (OptionsInfo["Outfile"])) 392 393 def RetrieveOptions(): 394 """Retrieve command line arguments and options.""" 395 396 # Get options... 397 global Options 398 Options = docopt(_docoptUsage_) 399 400 # Set current working directory to the specified directory... 401 WorkingDir = Options["--workingdir"] 402 if WorkingDir: 403 os.chdir(WorkingDir) 404 405 # Handle examples option... 406 if "--examples" in Options and Options["--examples"]: 407 MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_)) 408 sys.exit(0) 409 410 def ValidateOptions(): 411 """Validate option values.""" 412 413 MiscUtil.ValidateOptionTextValue("-a, --alignMethod", Options["--alignMethod"], "align cealign super") 414 MiscUtil.ValidateOptionTextValue("--alignMode", Options["--alignMode"], "FirstChain Complex") 415 416 MiscUtil.ValidateOptionTextValue("-m, --mode", Options["--mode"], "OneToOne AllToAll FirstToAll") 417 418 # Expand reffiles to handle presence of multiple input files... 419 RefFilesNames = MiscUtil.ExpandFileNames(Options["--reffiles"], ",") 420 421 # Validate file extensions... 422 for RefFile in RefFilesNames: 423 MiscUtil.ValidateOptionFilePath("-r, --reffiles", RefFile) 424 MiscUtil.ValidateOptionFileExt("-r, --reffiles", RefFile, "pdb cif") 425 Options["--refFilesNames"] = RefFilesNames 426 427 # Expand probefiles to handle presence of multiple input files... 428 ProbeFilesNames = MiscUtil.ExpandFileNames(Options["--probefiles"], ",") 429 430 # Validate file extensions... 431 for ProbeFile in ProbeFilesNames: 432 MiscUtil.ValidateOptionFilePath("-p, --probefiles", ProbeFile) 433 MiscUtil.ValidateOptionFileExt("-p, --probefiles", ProbeFile, "pdb cif") 434 Options["--probeFilesNames"] = ProbeFilesNames 435 436 MiscUtil.ValidateOptionFileExt("-o, --outfile", Options["--outfile"], "csv tsv txt") 437 MiscUtil.ValidateOptionsOutputFileOverwrite("-o, --outfile", Options["--outfile"], "--overwrite", Options["--overwrite"]) 438 439 MiscUtil.ValidateOptionTextValue("--outMatrix", Options["--outMatrix"], "Yes No") 440 441 # Setup a usage string for docopt... 442 _docoptUsage_ = """ 443 PyMOLCalculateRMSD.py - Calculate RMSD between macromolecules 444 445 Usage: 446 PyMOLCalculateRMSD.py [--alignMethod <align, cealign, super>] 447 [--alignMode <FirstChain or Complex>] [--mode <OneToOne, AllToAll, FirstToAll>] 448 [--outMatrix <yes or no>] [--overwrite] 449 [-w <dir>] -p <probefile1,probefile2,probefile3...> -r <reffile1,reffile2,reffile3...> -o <outfile> 450 PyMOLCalculateRMSD.py -h | --help | -e | --examples 451 452 Description: 453 Calculate Root Mean Square Distance (RMSD) between a set of similar 454 macromolecules in reference and probe input files. The probe and reference 455 files are spatially aligned before the the calculation of RMSD values. 456 457 The supported input file format are: PDB (.pdb), mmCIF (.cif) 458 459 The supported output file formats are: CSV (.csv), TSV (.tsv, .txt) 460 461 Options: 462 -a, --alignMethod <align, cealign, super> [default: super] 463 Alignment methodology to use for aligning probe input files to 464 reference files. 465 --alignMode <FirstChain or Complex> [default: FirstChain] 466 Portion of probe and reference files to use for spatial alignment of 467 probe files against reference files. Possible values: FirstChain or 468 Complex. 469 470 The FirstChain mode allows alignment of the first chain in probe files 471 to the first chain in reference files along with moving the rest of the 472 complex to coordinate space of the reference files. The complete 473 complex in probe files is aligned to the complete complex in reference 474 files for the Complex mode. 475 -e, --examples 476 Print examples. 477 -h, --help 478 Print this help message. 479 -m, --mode <OneToOne, AllToAll, FirstToAll> [default: OneToOne] 480 Specify how reference and probe input files are handled during the calculation 481 of RMSD between reference and probe files. Possible values: OneToOne, 482 AllToAll and AllToFirst. For OneToOne mode, the number of reference input 483 files must be equal to the number of probe input files. The RMSD is 484 calculated for each pair of reference and probe file and written to the 485 output file. For AllToAll mode, the RMSD is calculated for each reference 486 input file against all probe input files. For FirstToAll mode, however, the RMSD 487 is only calculated for the first reference input file against all probe files. 488 -p, --probefiles <probefile1,probefile2,probelfile3...> 489 A comma delimited list of probe input files. The wildcards are also allowed 490 in file names. 491 -r, --reffiles <reffile1,reffile2,reffile3...> 492 A comma delimited list of reference input files. The wildcards are also allowed 493 in file names. 494 -o, --outfile <outfile> 495 Output file name for writing out RMSD values. Supported text file extensions: 496 csv, tsv or txt. 497 --outMatrix <yes or no> [default: yes] 498 Output file in a matrix format during 'AllToAll' value for '-m, --mode' option. 499 --overwrite 500 Overwrite existing files. 501 -w, --workingdir <dir> 502 Location of working directory which defaults to the current directory. 503 504 Examples: 505 To calculate RMSD between pair of macromolecules in reference and probe files 506 using only first chain in each file and write out a CSV file containing calculated RMSD 507 values along with IDs, type: 508 509 % PyMOLCalculateRMSD.py -r "Sample3.pdb,Sample4.pdb,Sample5.pdb" 510 -p "Sample3.pdb,Sample4.pdb,Sample5.pdb" -o SampleOut.csv 511 512 To calculate RMSD between all macromolecules in reference and probe files using 513 complete complex and write out a CSV matrix file, type: 514 515 % PyMOLCalculateRMSD.py -m AllToAll --alignMode Complex 516 --outMatrix Yes -r "Sample3.pdb,Sample4.pdb,Sample5.pdb" 517 -p "Sample3.pdb,Sample4.pdb" -o SampleOut.csv 518 519 To calculate RMSD between macromolecule in first reference against all probe files 520 using only first chain in each file and write out a TSV file containing calculated RMSD 521 values along with IDs, type: 522 523 % PyMOLCalculateRMSD.py -m FirstToAll 524 -r "Sample3.pdb,Sample4.pdb,Sample5.pdb" 525 -p "Sample3.pdb,Sample4.pdb,Sample5.pdb" -o SampleOut.tsv 526 527 To calculate RMSD between pair of macromolecules in reference and probe files 528 using only first chain in each file along with a specific alignment method and write 529 out a CSV file containing calculated RMSD values, type: 530 531 % PyMOLCalculateRMSD.py --alignMethod align 532 -r "Sample3.pdb,Sample4.pdb,Sample5.pdb" 533 -p "Sample3.pdb,Sample4.pdb,Sample5.pdb" -o SampleOut.csv 534 535 Author: 536 Manish Sud(msud@san.rr.com) 537 538 See also: 539 PyMOLAlignChains.py, PyMOLSplitChainsAndLigands.py, 540 PyMOLVisualizeMacromolecules.py 541 542 Copyright: 543 Copyright (C) 2024 Manish Sud. All rights reserved. 544 545 The functionality available in this script is implemented using PyMOL, a 546 molecular visualization system on an open source foundation originally 547 developed by Warren DeLano. 548 549 This file is part of MayaChemTools. 550 551 MayaChemTools is free software; you can redistribute it and/or modify it under 552 the terms of the GNU Lesser General Public License as published by the Free 553 Software Foundation; either version 3 of the License, or (at your option) any 554 later version. 555 556 """ 557 558 if __name__ == "__main__": 559 main()