1#A* ------------------------------------------------------------------- 2#B* This file contains source code for the PyMOL computer program 3#C* Copyright (c) Schrodinger, LLC. 4#D* ------------------------------------------------------------------- 5#E* It is unlawful to modify or remove this copyright notice. 6#F* ------------------------------------------------------------------- 7#G* Please see the accompanying LICENSE file for further information. 8#H* ------------------------------------------------------------------- 9#I* Additional authors of this source file include: 10#-* 11#-* 12#-* 13#Z* ------------------------------------------------------------------- 14 15from __future__ import print_function 16 17if True: 18 19 cmd = __import__("sys").modules["pymol.cmd"] 20 from .cmd import _cmd,lock,unlock 21 from . import selector 22 import os 23 import pymol 24 25 from .cmd import _cmd,lock,unlock,Shortcut, \ 26 DEFAULT_ERROR, DEFAULT_SUCCESS, _raising, is_ok, is_error 27 28 29 def cealign(target, mobile, target_state=1, mobile_state=1, quiet=1, 30 guide=1, d0=3.0, d1=4.0, window=8, gap_max=30, transform=1, 31 object=None, _self=cmd): 32 ''' 33DESCRIPTION 34 35 "cealign" aligns two proteins using the CE algorithm. 36 37USAGE 38 39 cealign target, mobile [, target_state [, mobile_state [, quiet [, 40 guide [, d0 [, d1 [, window [, gap_max, [, transform ]]]]]]]]] 41 42NOTES 43 44 If "guide" is set PyMOL will align using only alpha carbons, which is the 45 default behavior. Otherwise, PyMOL will use all atoms. If "quiet" is set 46 to -1, PyMOL will print the rotation matrix as well. 47 48 Reference: Shindyalov IN, Bourne PE (1998) Protein structure alignment by 49 incremental combinatorial extension (CE) of the optimal path. Protein 50 Engineering 11(9) 739-747. 51 52EXAMPLES 53 54 cealign protA////CA, protB////CA 55 56 # fetch two proteins and align them 57 fetch 1rlw 1rsy, async=0 58 cealign 1rlw, 1rsy 59 60SEE ALSO 61 62 align, pair_fit, fit, rms, rms_cur, intra_rms, intra_rms_cur, super 63 ''' 64 quiet = int(quiet) 65 window = int(window) 66 guide = "" if int(guide)==0 else "and guide" 67 68 mobile = "(%s) %s" % (mobile,guide) 69 target = "(%s) %s" % (target,guide) 70 71 # handle PyMOL's macro /// notation 72 mobile = selector.process(mobile) 73 target = selector.process(target) 74 75 # make the lists for holding coordinates and IDs 76 mod1 = _self.get_model(target, state=target_state) 77 mod2 = _self.get_model(mobile, state=mobile_state) 78 sel1 = mod1.get_coord_list() 79 sel2 = mod2.get_coord_list() 80 ids1 = [a.id for a in mod1.atom] 81 ids2 = [a.id for a in mod2.atom] 82 83 if len(sel1) < 2 * window: 84 print("CEalign-Error: Your target selection is too short.") 85 raise pymol.CmdException 86 if len(sel2) < 2 * window: 87 print("CEalign-Error: Your mobile selection is too short.") 88 raise pymol.CmdException 89 if window < 3: 90 print("CEalign-Error: window size must be an integer greater than 2.") 91 raise pymol.CmdException 92 if int(gap_max) < 0: 93 print("CEalign-Error: gap_max must be a positive integer.") 94 raise pymol.CmdException 95 96 r = DEFAULT_ERROR 97 98 try: 99 _self.lock(_self) 100 101 # call the C function 102 r = _cmd.cealign( _self._COb, sel1, sel2, float(d0), float(d1), int(window), int(gap_max) ) 103 104 (aliLen, RMSD, rotMat, i1, i2) = r 105 if quiet==-1: 106 import pprint 107 print("RMSD %f over %i residues" % (float(RMSD), int(aliLen))) 108 print("TTT Matrix:") 109 pprint.pprint(rotMat) 110 elif quiet==0: 111 print("RMSD %f over %i residues" % (float(RMSD), int(aliLen))) 112 113 if int(transform): 114 for model in _self.get_object_list("(" + mobile + ")"): 115 _self.transform_object(model, rotMat, state=0) 116 117 if object is not None: 118 obj1 = _self.get_object_list("(" + target + ")") 119 obj2 = _self.get_object_list("(" + mobile + ")") 120 if len(obj1) > 1 or len(obj2) > 1: 121 print(' CEalign-Error: selection spans multiple' + \ 122 ' objects, cannot create alignment object') 123 raise pymol.CmdException 124 tmp1 = _self.get_unused_name('_1') 125 tmp2 = _self.get_unused_name('_2') 126 _self.select_list(tmp1, obj1[0], [ids1[j] for i in i1 for j in range(i, i+window)]) 127 _self.select_list(tmp2, obj2[0], [ids2[j] for i in i2 for j in range(i, i+window)]) 128 _self.rms_cur(tmp2, tmp1, cycles=0, matchmaker=4, object=object) 129 _self.delete(tmp1) 130 _self.delete(tmp2) 131 except SystemError: 132 # findBest might return NULL, which raises SystemError 133 print(" CEalign-Error: alignment failed") 134 finally: 135 _self.unlock(r,_self) 136 if _self._raising(r,_self): raise pymol.CmdException 137 return ( {"alignment_length": aliLen, "RMSD" : RMSD, "rotation_matrix" : rotMat } ) 138 139 def extra_fit(selection='(all)', reference='', method='align', zoom=1, 140 quiet=0, _self=cmd, **kwargs): 141 ''' 142DESCRIPTION 143 144 Like "intra_fit", but for multiple objects instead of 145 multiple states. 146 147ARGUMENTS 148 149 selection = string: atom selection of multiple objects {default: all} 150 151 reference = string: reference object name {default: first object in selection} 152 153 method = string: alignment method (command that takes "mobile" and "target" 154 arguments, like "align", "super", "cealign" {default: align} 155 156 ... extra arguments are passed to "method" 157 158SEE ALSO 159 160 align, super, cealign, intra_fit, util.mass_align 161 ''' 162 zoom, quiet = int(zoom), int(quiet) 163 sele_name = _self.get_unused_name('_') 164 _self.select(sele_name, selection, 0) 165 models = _self.get_object_list(sele_name) 166 167 if not reference: 168 reference = models[0] 169 models = models[1:] 170 elif reference in models: 171 models.remove(reference) 172 else: 173 _self.select(sele_name, reference, merge=1) 174 175 if _self.is_string(method): 176 if method in _self.keyword: 177 method = _self.keyword[method][0] 178 else: 179 raise pymol.CmdException(method, 'Unknown method') 180 181 for model in models: 182 x = method(mobile='?%s & ?%s' % (sele_name, model), 183 target='?%s & ?%s' % (sele_name, reference), **kwargs) 184 if not quiet: 185 if _self.is_sequence(x): 186 print('%-20s RMSD = %8.3f (%d atoms)' % (model, x[0], x[1])) 187 elif isinstance(x, float): 188 print('%-20s RMSD = %8.3f' % (model, x)) 189 elif isinstance(x, dict) and 'RMSD' in x: 190 natoms = x.get('alignment_length', 0) 191 suffix = (' (%s atoms)' % natoms) if natoms else '' 192 print('%-20s RMSD = %8.3f' % (model, x['RMSD']) + suffix) 193 else: 194 print('%-20s' % (model,)) 195 196 if zoom: 197 _self.zoom(sele_name) 198 _self.delete(sele_name) 199 200 201 def alignto(target='', method="cealign", selection='', quiet=1, _self=cmd, **kwargs): 202 """ 203DESCRIPTION 204 205 "alignto" aligns all other loaded objects to the target 206 using the specified alignment algorithm. 207 208USAGE 209 210 alignto target [, method [, quiet ]] 211 212NOTES 213 214 Available alignment methods are "align", "super" and "cealign". 215 216EXAMPLE 217 218 # fetch some calmodulins 219 fetch 1cll 1sra 1ggz 1k95, async=0 220 221 # align them to 1cll using cealign 222 alignto 1cll, method=cealign 223 alignto 1cll, object=all_to_1cll 224 225SEE ALSO 226 227 extra_fit, align, super, cealign, fit, rms, rms_cur, intra_fit 228 """ 229 if not selection: 230 names = _self.get_names("public_objects", 1) 231 if not names: 232 raise pymol.CmdException('no public objects') 233 selection = '%' + ' %'.join(names) 234 return extra_fit(selection, target, method, 0, quiet, _self, **kwargs) 235 236 237 238 def super(mobile, target, cutoff=2.0, cycles=5, 239 gap=-1.5, extend=-0.7, max_gap=50, object=None, 240 matrix="BLOSUM62", mobile_state=0, target_state=0, 241 quiet=1, max_skip=0, transform=1, reset=0, 242 seq=0.0, radius=12.0, scale=17.0, base=0.65, 243 coord=0.0, expect=6.0, window=3, ante=-1.0, 244 _self=cmd): 245 246 ''' 247DESCRIPTION 248 249 "super" performs a residue-based pairwise alignment followed by a 250 structural superposition, and then carries out zero or more cycles 251 of refinement in order to reject outliers. 252 253USAGE 254 255 super mobile, target [, object=name ] 256 257NOTES 258 259 By adjusting various parameters, the nature of the initial 260 alignment can be modified to include or exclude various factors 261 including sequence similarity, main chain path, secondary & 262 tertiary structure, and current coordinates. 263 264EXAMPLE 265 266 super protA////CA, protB////CA, object=supeAB 267 268SEE ALSO 269 270 align, pair_fit, fit, rms, rms_cur, intra_rms, intra_rms_cur 271 ''' 272 r = DEFAULT_ERROR 273 mobile = selector.process(mobile) 274 target = selector.process(target) 275 if object is None: object='' 276 matrix = str(matrix) 277 if matrix.lower() in ['none', '']: 278 mfile = '' 279 elif os.path.exists(matrix): 280 mfile = matrix 281 else: 282 mfile = cmd.exp_path("$PYMOL_DATA/pymol/matrices/"+matrix) 283 # delete existing alignment object (if asked to reset it) 284 try: 285 _self.lock(_self) 286 287 r = _cmd.align(_self._COb,mobile,"("+target+")",float(cutoff), 288 int(cycles),float(gap),float(extend),int(max_gap), 289 str(object),str(mfile), 290 int(mobile_state)-1,int(target_state)-1, 291 int(quiet),int(max_skip),int(transform), 292 int(reset),float(seq), 293 float(radius),float(scale),float(base), 294 float(coord),float(expect),int(window), 295 float(ante)) 296 297 finally: 298 _self.unlock(r,_self) 299 if _self._raising(r,_self): raise pymol.CmdException 300 return r 301 302 def align(mobile, target, cutoff=2.0, cycles=5, gap=-10.0, 303 extend=-0.5, max_gap=50, object=None, 304 matrix="BLOSUM62", mobile_state=0, target_state=0, 305 quiet=1, max_skip=0, transform=1, reset=0, _self=cmd): 306 307 ''' 308DESCRIPTION 309 310 "align" performs a sequence alignment followed by a structural 311 superposition, and then carries out zero or more cycles of 312 refinement in order to reject structural outliers found during 313 the fit. "align" does a good job on proteins with decent sequence 314 similarity (identity >30%). For comparing proteins with lower 315 sequence identity, the "super" and "cealign" commands perform 316 better. 317 318USAGE 319 320 align mobile, target [, cutoff [, cycles 321 [, gap [, extend [, max_gap [, object 322 [, matrix [, mobile_state [, target_state 323 [, quiet [, max_skip [, transform [, reset ]]]]]]]]]]]]] 324 325ARGUMENTS 326 327 mobile = string: atom selection of mobile object 328 329 target = string: atom selection of target object 330 331 cutoff = float: outlier rejection cutoff in Angstrom {default: 2.0} 332 333 cycles = int: maximum number of outlier rejection cycles {default: 5} 334 335 gap, extend, max_gap: sequence alignment parameters 336 337 object = string: name of alignment object to create {default: (no 338 alignment object)} 339 340 matrix = string: file name of substitution matrix for sequence 341 alignment {default: BLOSUM62} 342 343 mobile_state = int: object state of mobile selection {default: 0 = all states} 344 345 target_state = int: object state of target selection {default: 0 = all states} 346 347 transform = 0/1: do superposition {default: 1} 348 349NOTES 350 351 If object is specified, then align will create an object which 352 indicates paired atoms and supports visualization of the alignment 353 in the sequence viewer. 354 355 The RMSD of the aligned atoms (after outlier rejection!) is reported 356 in the text output. The all-atom RMSD can be obtained by setting 357 cycles=0 and thus not doing any outlier rejection. 358 359EXAMPLE 360 361 align protA////CA, protB////CA, object=alnAB 362 363SEE ALSO 364 365 super, cealign, pair_fit, fit, rms, rms_cur, intra_rms, intra_rms_cur 366 ''' 367 r = DEFAULT_ERROR 368 mobile = selector.process(mobile) 369 target = selector.process(target) 370 matrix = str(matrix) 371 if matrix.lower() in ['none', '']: 372 mfile = '' 373 elif os.path.exists(matrix): 374 mfile = matrix 375 else: 376 mfile = cmd.exp_path("$PYMOL_DATA/pymol/matrices/"+matrix) 377 if object is None: object='' 378 # delete existing alignment object (if asked to reset it) 379 try: 380 _self.lock(_self) 381 r = _cmd.align(_self._COb,mobile,"("+target+")", 382 float(cutoff),int(cycles),float(gap), 383 float(extend),int(max_gap),str(object),str(mfile), 384 int(mobile_state)-1,int(target_state)-1, 385 int(quiet),int(max_skip),int(transform),int(reset), 386 -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0.0) 387 finally: 388 _self.unlock(r,_self) 389 if _self._raising(r,_self): raise pymol.CmdException 390 return r 391 392 def intra_fit(selection, state=1, quiet=1, mix=0, _self=cmd): 393 ''' 394DESCRIPTION 395 396 "intra_fit" fits all states of an object to an atom selection 397 in the specified state. It returns the rms values to python 398 as an array. 399 400USAGE 401 402 intra_fit selection [, state] 403 404ARGUMENTS 405 406 selection = string: atoms to fit 407 408 state = integer: target state 409 410PYMOL API 411 412 cmd.intra_fit( string selection, int state ) 413 414EXAMPLES 415 416 intra_fit ( name CA ) 417 418PYTHON EXAMPLE 419 420 from pymol import cmd 421 rms = cmd.intra_fit("(name CA)",1) 422 423SEE ALSO 424 425 fit, rms, rms_cur, intra_rms, intra_rms_cur, pair_fit 426 ''' 427 # preprocess selection 428 selection = selector.process(selection) 429 # 430 r = DEFAULT_ERROR 431 state = int(state) 432 mix = int(mix) 433 try: 434 _self.lock(_self) 435 r = _cmd.intrafit(_self._COb,"("+str(selection)+")",int(state)-1,2,int(quiet),int(mix)) 436 finally: 437 _self.unlock(r,_self) 438 if not isinstance(r, list): 439 r = DEFAULT_ERROR 440 elif not quiet: 441 st = 1 442 for a in r: 443 if a>=0.0: 444 if mix: 445 print(" cmd.intra_fit: %5.3f in state %d vs mixed target"%(a,st)) 446 else: 447 print(" cmd.intra_fit: %5.3f in state %d vs state %d"%(a,st,state)) 448 st = st + 1 449 if _self._raising(r,_self): raise pymol.CmdException 450 return r 451 452 def intra_rms(selection, state=0, quiet=1, _self=cmd): 453 ''' 454DESCRIPTION 455 456 "intra_rms" calculates rms fit values for all states of an object 457 over an atom selection relative to the indicated state. 458 Coordinates are left unchanged. The rms values are returned as a 459 python array. 460 461EXAMPLE 462 463 from pymol import cmd 464 rms = cmd.intra_rms("(name CA)",1) 465 print rms 466 467PYMOL API 468 469 cmd.intra_rms(string selection, int state) 470 471SEE ALSO 472 473 fit, rms, rms_cur, intra_fit, intra_rms_cur, pair_fit 474 ''' 475 # preprocess selection 476 selection = selector.process(selection) 477 # 478 r = DEFAULT_ERROR 479 state = int(state) 480 try: 481 _self.lock(_self) 482 r = _cmd.intrafit(_self._COb,"("+str(selection)+")",int(state)-1,1,int(quiet),int(0)) 483 finally: 484 _self.unlock(r,_self) 485 if not isinstance(r, list): 486 r = DEFAULT_ERROR 487 elif not quiet: 488 st = 1 489 for a in r: 490 if a>=0.0: 491 print(" cmd.intra_rms: %5.3f in state %d vs state %d"%(a,st,state)) 492 st = st + 1 493 if _self._raising(r,_self): raise pymol.CmdException 494 return r 495 496 def intra_rms_cur(selection, state=0, quiet=1, _self=cmd): 497 ''' 498DESCRIPTION 499 500 "intra_rms_cur" calculates rms values for all states of an object 501 over an atom selection relative to the indicated state without 502 performing any fitting. The rms values are returned 503 as a python array. 504 505PYMOL API 506 507 cmd.intra_rms_cur( string selection, int state) 508 509PYTHON EXAMPLE 510 511 from pymol import cmd 512 rms = cmd.intra_rms_cur("(name CA)",1) 513 514SEE ALSO 515 516 fit, rms, rms_cur, intra_fit, intra_rms, pair_fit 517 ''' 518 # preprocess selection 519 selection = selector.process(selection) 520 # 521 r = DEFAULT_ERROR 522 state = int(state) 523 try: 524 _self.lock(_self) 525 r = _cmd.intrafit(_self._COb,"("+str(selection)+")",int(state)-1,0,int(quiet),int(0)) 526 finally: 527 _self.unlock(r,_self) 528 if not isinstance(r, list): 529 r = DEFAULT_ERROR 530 elif not quiet: 531 st = 1 532 for a in r: 533 if a>=0.0: 534 print(" cmd.intra_rms_cur: %5.3f in state %d vs state %d"%(a,st,state)) 535 st = st + 1 536 if _self._raising(r,_self): raise pymol.CmdException 537 return r 538 539 def fit(mobile, target, mobile_state=0, target_state=0, 540 quiet=1, matchmaker=0, cutoff=2.0, cycles=0, object=None, _self=cmd): 541 ''' 542DESCRIPTION 543 544 "fit" superimposes the model in the first selection on to the model 545 in the second selection. Only matching atoms in both selections will 546 be used for the fit. 547 548USAGE 549 550 fit mobile, target [, mobile_state [, target_state [, quiet 551 [, matchmaker [, cutoff [, cycles [, object ]]]]]]] 552 553ARGUMENTS 554 555 mobile = string: atom selection 556 557 target = string: atom selection 558 559 mobile_state = integer: object state {default=0, all states) 560 561 target_state = integer: object state {default=0, all states) 562 563 matchmaker = integer: how to match atom pairs {default: 0} 564 -1: assume that atoms are stored in the identical order 565 0/1: match based on all atom identifiers (segi,chain,resn,resi,name,alt) 566 2: match based on ID 567 3: match based on rank 568 4: match based on index (same as -1 ?) 569 570 cutoff = float: outlier rejection cutoff (only if cycles>0) {default: 2.0} 571 572 cycles = integer: number of cycles in outlier rejection refinement {default: 0} 573 574 object = string: name of alignment object to create {default: None} 575 576EXAMPLES 577 578 fit protA, protB 579 580NOTES 581 582 Since atoms are matched based on all of their identifiers 583 (including segment and chain identifiers), this command is only 584 helpful when comparing very similar structures. 585 586SEE ALSO 587 588 align, super, pair_fit, rms, rms_cur, intra_fit, intra_rms, intra_rms_cur 589 ''' 590 r = DEFAULT_ERROR 591 a=str(mobile) 592 b=str(target) 593 # preprocess selections 594 a = selector.process(a) 595 b = selector.process(b) 596 # 597 if object is None: object='' 598 if int(matchmaker)==0: 599 sele1 = "((%s) in (%s))" % (str(a),str(b)) 600 sele2 = "((%s) in (%s))" % (str(b),str(a)) 601 else: 602 sele1 = str(a) 603 sele2 = str(b) 604 try: 605 _self.lock(_self) 606 r = _cmd.fit(_self._COb,sele1,sele2,2, 607 int(mobile_state)-1,int(target_state)-1, 608 int(quiet),int(matchmaker),float(cutoff), 609 int(cycles),str(object)) 610 finally: 611 _self.unlock(r,_self) 612 if r < -0.5: 613 raise pymol.CmdException 614 return r 615 616 def rms(mobile, target, mobile_state=0, target_state=0, quiet=1, 617 matchmaker=0, cutoff=2.0, cycles=0, object=None, _self=cmd): 618 ''' 619DESCRIPTION 620 621 "rms" computes a RMS fit between two atom selections, but does not 622 tranform the models after performing the fit. 623 624USAGE 625 626 rms (selection), (target-selection) 627 628EXAMPLES 629 630 fit ( mutant and name CA ), ( wildtype and name CA ) 631 632SEE ALSO 633 634 fit, rms_cur, intra_fit, intra_rms, intra_rms_cur, pair_fit 635 ''' 636 r = DEFAULT_ERROR 637 a=str(mobile) 638 b=str(target) 639 # preprocess selections 640 a = selector.process(a) 641 b = selector.process(b) 642 # 643 if object is None: object='' 644 if int(matchmaker)==0: 645 sele1 = "((%s) in (%s))" % (str(a),str(b)) 646 sele2 = "((%s) in (%s))" % (str(b),str(a)) 647 else: 648 sele1 = str(a) 649 sele2 = str(b) 650 try: 651 _self.lock(_self) 652 r = _cmd.fit(_self._COb,sele1,sele2,1, 653 int(mobile_state)-1,int(target_state)-1, 654 int(quiet),int(matchmaker),float(cutoff), 655 int(cycles),str(object)) 656 finally: 657 _self.unlock(r,_self) 658 if r < -0.5: 659 raise pymol.CmdException 660 return r 661 662 def rms_cur(mobile, target, mobile_state=0, target_state=0, 663 quiet=1, matchmaker=0, cutoff=2.0, cycles=0, 664 object=None, _self=cmd): 665 666 ''' 667DESCRIPTION 668 669 "rms_cur" computes the RMS difference between two atom 670 selections without performing any fitting. 671 672USAGE 673 674 rms_cur (selection), (selection) 675 676SEE ALSO 677 678 fit, rms, intra_fit, intra_rms, intra_rms_cur, pair_fit 679 ''' 680 r = DEFAULT_ERROR 681 a=str(mobile) 682 b=str(target) 683 # preprocess selections 684 a = selector.process(a) 685 b = selector.process(b) 686 # 687 if object is None: object='' 688 if int(matchmaker)==0: 689 sele1 = "((%s) in (%s))" % (str(a),str(b)) 690 sele2 = "((%s) in (%s))" % (str(b),str(a)) 691 else: 692 sele1 = str(a) 693 sele2 = str(b) 694 try: 695 _self.lock(_self) 696 r = _cmd.fit(_self._COb,sele1,sele2,0, 697 int(mobile_state)-1,int(target_state)-1, 698 int(quiet),int(matchmaker),float(cutoff), 699 int(cycles),str(object)) 700 finally: 701 _self.unlock(r,_self) 702 if r < -0.5: 703 raise pymol.CmdException 704 return r 705 706 def pair_fit(*arg, **kw): 707 ''' 708DESCRIPTION 709 710 "pair_fit" fits matched sets of atom pairs between two objects. 711 712USAGE 713 714 pair_fit selection, selection, [ selection, selection [ ... ]] 715 716EXAMPLES 717 718 # superimpose protA residues 10-25 and 33-46 to protB residues 22-37 and 41-54: 719 720 pair_fit protA/10-25+33-46/CA, protB/22-37+41-54/CA 721 722 # superimpose ligA atoms C1, C2, and C4 to ligB atoms C8, C4, and C10, respectively: 723 724 pair_fit ligA////C1, ligB////C8, ligA////C2, ligB////C4, ligA////C3, ligB////C10 725 726NOTES 727 728 So long as the atoms are stored in PyMOL with the same order 729 internally, you can provide just two selections. Otherwise, you 730 may need to specify each pair of atoms separately, two by two, as 731 additional arguments to pair_fit. 732 733 Script files are usually recommended when using this command. 734 735SEE ALSO 736 737 fit, rms, rms_cur, intra_fit, intra_rms, intra_rms_cur 738 ''' 739 _self = kw.pop('_self',cmd) 740 quiet = int(kw.pop('quiet', 0)) 741 742 if kw: 743 raise pymol.CmdException('unexpected keyword arguments: ' + str(list(kw))) 744 745 r = DEFAULT_ERROR 746 if len(arg) < 2: 747 raise pymol.CmdException('need at least 2 selection') 748 if len(arg) % 2: 749 raise pymol.CmdException('need even number of selections') 750 new_arg = list(map(selector.process, arg)) 751 try: 752 _self.lock(_self) 753 r = _cmd.fit_pairs(_self._COb,new_arg, quiet) 754 finally: 755 _self.unlock(r,_self) 756 if r < -0.5: 757 raise pymol.CmdException 758 return r 759