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