Difference between revisions of "User:Remig/plico/tug"

From Jmol
Jump to navigation Jump to search
(Support other modules)
(Avoid "axis," a newly reserved word)
 
(12 intermediate revisions by the same user not shown)
Line 1: Line 1:
'''Tug''' allows the user to pull or push by mouse actions to move or rotate one part of a polypeptide against the rest by rotation on its psi and phi bonds with collision detection and restriction.  It also allows the user to move an entire chain to nest against another chain.
+
'''Tug''' allows the user to pull or push by mouse actions to move or rotate one part of a polypeptide against the rest by rotation on its psi and phi bonds with collision detection and restriction.  It also allows the user to move an entire chain to nest against another chain. A full description of its capabilities can be found [[User:Remig/plico/tugManual|here]].
  
 
'''Tug''' is a member of the Plico suite of protein folding tools described in [[User:Remig/plico]] . It may be installed and accessed as a macro with the file:
 
'''Tug''' is a member of the Plico suite of protein folding tools described in [[User:Remig/plico]] . It may be installed and accessed as a macro with the file:
 
<pre>Title=PLICO Tug
 
<pre>Title=PLICO Tug
Script=script <path to your scripts folder>/tug.spt;plicotug</pre>
+
Script=script <path to your scripts directory>/tug.spt;plico_tug</pre>
saved as plicotug.macro in your .jmol/macros folder as described in [[Macro]].
+
saved as plicotug.macro in your .jmol/macros directory as described in [[Macro]].
  
Copy and paste the following into a text editor and save in your scripts folder as tug.spt.  
+
Copy and paste the following into a text editor and save in your scripts directory as tug.spt.  
 
<pre>#  tug - Jmol script by Ron Mignery
 
<pre>#  tug - Jmol script by Ron Mignery
#  v1.4 beta    2/15/2014 for Jmol 14
+
#  v1.20 beta    4/12/2016 -axis is now a reserved word
 
#
 
#
 
#  Translate or rotate a stretch of a polypeptide against itself
 
#  Translate or rotate a stretch of a polypeptide against itself
 
#    or against other chains by mouse actions
 
#    or against other chains by mouse actions
 
#
 
#
var kTug = 3
+
kTug = 3
var kDtolerance = 0.2
+
gCanchorIdx = -1
var kAtolerance = 5.0
+
gCanchorNo = -1
var kCtolerance = 1.85
+
gPlico = "TUG"
var kMtolerance = 0.8
+
gNanchorIdx = -1
var gCanchorIdx = -1
+
gNanchorNo = -1
var gCanchorNo = -1
+
gCcargoIdx = -1
var gPlico = "TUG"
+
gNcargoIdx = -1
var gNanchorIdx = -1
+
gCcargoNo = -1
var gNanchorNo = -1
+
gNcargoNo = -1
var gCcargoIdx = -1
+
gDestAtomIdx = -1
var gNcargoIdx = -1
+
g1pivotIdx = -1
var gCcargoNo = -1
+
g2pivotIdx = -1
var gNcargoNo = -1
+
gSelSaves = ({})
var gDestAtomIdx = -1
+
gCrotors = array()
var g1pivotIdx = -1
+
gNrotors = array()
var g2pivotIdx = -1
+
gOkCollide = ({})
var gSelSaves = ({})
+
gChain = ""
var gCrotors = array()
+
gMinNo = 1
var gNrotors = array()
+
gMaxNo = 9999
var gMouseX = 0
+
gCargoSet = ({})
var gMouseY = 0
+
gMovingSet = ({})
var gOkCollide = ({})
+
gBusy = false
var gChain = ""
+
gSCidx = -1
var gMinNo = 1
+
gSCcircle = -1
var gMaxNo = 9999
+
gSCpt = {0 0 0}
var gScheme = "Jmol"
+
gTargetPt = {0 0 0}
var gAltScheme = "Rasmol"
+
gNewDrag = false
var gCargoSet = ({})
+
gTow = false
var gMovingSet = ({})
+
g1dynamicIdx = -1
var gBusy = FALSE
+
g2dynamicIdx = -1
var gSCidx = -1
+
gSCcheck = true
var gSCcircle = -1
+
gBondPicking = false
var gSCpt = {0 0 0}
+
gFreeze = array()
var gOK = TRUE # global return value to work around jmol *feature*
+
gToab = false
var gOk2 = TRUE # "    "
 
var gTargetPt = {0 0 0}
 
var gNewDrag = FALSE
 
var gEcho = ""
 
var gZoom = ""
 
var gRotate = ""
 
var gTow = FALSE
 
var g1dynamicIdx = -1
 
var g2dynamicIdx = -1
 
var gSCcheck = TRUE
 
  
# Return L tetrahedron point if i1<i2<i3, else R point
 
function getTet(i1, i2, i3, dist) {
 
    var v1 = {atomIndex=i3}.xyz - {atomIndex=i2}.xyz
 
    var v2 = {atomIndex=i1}.xyz - {atomIndex=i2}.xyz
 
    var axis = cross(v1, v2)
 
    var pma = ({atomIndex=i1}.xyz + {atomIndex=i3}.xyz)/2
 
    var pmo = {atomIndex=i2}.xyz + {atomIndex=i2}.xyz - pma
 
    var pt = pmo + (axis/axis)
 
  
    var v = pt - {atomIndex=i2}.xyz
+
function get_cp_idx (idx) {
    var cdist = distance(pt, {atomIndex=i2})
+
     var no = {atomIndex=idx}.atomno
    var factor = (dist/cdist)
+
     while ((no < gMaxNo) and ({(atomno=no) and (chain=gChain)
    var lpt = v * factor
+
        and thisModel}.atomName != "C")) {
 
+
         no++
    return lpt + {atomIndex=i2}.xyz
 
}
 
 
 
function getTrigonal(i1, i2, i3, dist) {
 
     var v1 = {atomIndex=i1}.xyz - {atomIndex=i2}.xyz
 
     var v2 = {atomIndex=i3}.xyz - {atomIndex=i2}.xyz
 
    var pt = {atomIndex=i2}.xyz - (v1 + v2)
 
 
 
    var v = pt - {atomIndex=i2}.xyz
 
    var cdist = distance(pt, {atomIndex=i2})
 
    var factor = (dist/cdist)
 
    var lpt = (v * factor)
 
 
 
    return lpt + {atomIndex=i2}.xyz
 
}
 
 
 
function abs( x) {
 
    if (x < 0) {
 
         x = -x
 
 
     }
 
     }
     return x
+
     return ({(atomno=no) and (chain=gChain) and thisModel}.atomIndex)
 
}
 
}
  
function getCAmNo (iNo) {
+
function get_cm_no (iNo) {
     while ((iNo > 0) and ({(atomno=iNo) and (chain=gChain)}.atomName != "CA")) {
+
     while ((iNo > 0) and ({(atomno=iNo) and (chain=gChain)
 +
        and thisModel}.atomName != "C")) {
 
         iNo--
 
         iNo--
 
     }
 
     }
Line 103: Line 67:
 
}
 
}
  
function getCAmIdx (idx) {
+
function get_nm_idx (idx) {
     var no = {atomIndex=idx and (chain=gChain)}.atomno
+
     var no = {atomIndex=idx}.atomno
     no = getCAmNo( no)
+
    while ((no > 0) and ({(atomno=no) and (chain=gChain)
     return ({(atomno=no) and (chain=gChain)}.atomIndex)
+
        and thisModel}.atomName != "N")) {
 +
        no--
 +
     }
 +
     return ({(atomno=no) and (chain=gChain) and thisModel}.atomIndex)
 
}
 
}
  
function getCApNo (iNo) {
+
function get_np_no (iNo) {
     while ((iNo < gMaxNo) and ({(atomno=iNo) and (chain=gChain)}.atomName != "CA")) {
+
     while ((iNo < gMaxNo) and ({(atomno=iNo) and (chain=gChain)
 +
        and thisModel}.atomName != "N")) {
 
         iNo++
 
         iNo++
 
     }
 
     }
Line 116: Line 84:
 
}
 
}
  
function getCpIdx (idx) {
+
function get_cb_idx (BBidx) {
     var no = {atomIndex=idx and (chain=gChain)}.atomno
+
     var no = {atomIndex=BBidx}.atomno
     no = getCpNo( no)
+
    var i = 1
     return ({(atomno=no) and (chain=gChain)}.atomIndex)
+
    for (; i < 5; i++) {
 +
        if ({(atomno=@{no+i}) and (chain=gChain)
 +
            and thisModel}.atomName == "CB") {
 +
            break
 +
        }
 +
     }
 +
     return {(atomno=@{no+i}) and (chain=gChain) and thisModel}.atomIndex
 
}
 
}
  
function getCpNo (iNo) {
+
function get_o_idx (BBidx) {
     while ((iNo < gMaxNo) and ({(atomno=iNo) and (chain=gChain)}.atomName != "C")) {
+
     var no = {atomIndex=BBidx}.atomno
         iNo++
+
    var i = 1
 +
    for (; i < 4; i++) {
 +
        if ({(atomno=@{no+i}) and (chain=gChain)
 +
            and thisModel}.atomName == "O") {
 +
            break
 +
         }
 
     }
 
     }
     return iNo
+
     return {(atomno=@{no+i}) and (chain=gChain) and thisModel}.atomIndex
 
}
 
}
  
function getCApIdx (idx) {
+
function get_nward_bb_idx (idx, iChain) {
     var no = {atomIndex=idx and (chain=gChain)}.atomno
+
     var no = {atomIndex=idx}.atomno - 1
     no = getCApNo( no)
+
     for (; no >= 0; no--) {
    return ({(atomno=no) and (chain=gChain)}.atomIndex)
+
        var atomName = {(atomno=no) and (chain=iChain)
}
+
            and thisModel}.atomName
 
+
        if ((atomName = "N") or (atomName = "C") or (atomName = "CA")) {
function getCmNo (iNo) {
+
            break
    while ((iNo > 0) and ({(atomno=iNo) and (chain=gChain)}.atomName != "C")) {
+
         }
         iNo--
 
 
     }
 
     }
     return iNo
+
     return ((no >= 0) ? ({(atomno=no) and (chain=iChain)
 +
        and thisModel}.atomIndex) : -1)
 
}
 
}
  
function getCmIdx (idx) {
+
function get_cward_bb_idx (idx, iChain) {
     var no = {atomIndex=idx and (chain=gChain)}.atomno
+
     var no = {atomIndex=idx}.atomno + 1
     no = getCmNo( no)
+
     for (; no < gMaxNo; no++) {
    return ({(atomno=no) and (chain=gChain)}.atomIndex)
+
        var atomName = {(atomno=no) and (chain=iChain)
}
+
            and thisModel}.atomName
 
+
        if ((atomName = "N") or (atomName = "C") or (atomName = "CA")) {
function getNmNo (iNo) {
+
            break
    while ((iNo > 0) and ({(atomno=iNo) and (chain=gChain)}.atomName != "N")) {
+
         }
         iNo--
 
 
     }
 
     }
     return iNo
+
     return ((no >= 0) ? ({(atomno=no) and (chain=iChain)
 +
        and thisModel}.atomIndex) : -1)
 
}
 
}
  
function getNmIdx (idx) {
+
function get_sc_set (scIdx, iChain) {
     var no = {atomIndex=idx and (chain=gChain)}.atomno
+
     var scSet = ({})
     no = getNmNo( no)
+
     var idx = get_sc_bb_idx(scIdx, iChain)
     return ({(atomno=no) and (chain=gChain)}.atomIndex)
+
     var iNo = {atomIndex=idx}.atomno + 3
}
 
  
function getNpNo (iNo) {
+
    for (var i = 1; i < 20; i++) {
    while ((iNo < gMaxNo) and ({(atomno=iNo) and (chain=gChain)}.atomName != "N")) {
+
        idx = {(atomno=@{iNo+i}) and (chain=iChain)
         iNo++
+
            and thisModel}.atomIndex
 +
        if (is_bb_idx(idx)) {
 +
            break
 +
        }
 +
         scSet = scSet or {atomIndex=idx}
 
     }
 
     }
     return iNo
+
     return scSet
 
}
 
}
  
function getNpIdx (idx) {
+
function get_sc_bb_idx (idx, iChain) {
 
     var no = {atomIndex=idx}.atomno
 
     var no = {atomIndex=idx}.atomno
     no = getNpNo( no)
+
     for (; no > 0; no--) {
    return ({(atomno=no) and (chain=gChain)}.atomIndex)
+
        if ({(atomno=no) and (chain=iChain)
}
+
            and thisModel}.atomName == "CA") {
 
+
            break
function getCBidx (BBidx) {
+
        }
    var no = {atomIndex=BBidx}.atomno
+
        else if ({(atomno=no) and (chain=iChain)
    var i = 1
+
            and thisModel}.atomName == "C") {
    for (; i < 5; i++) {
+
            break
         if ({(atomno=@{no+i}) and (chain=gChain)}.atomName == "CB") {
+
         }
 +
        else if ({(atomno=no) and (chain=iChain)
 +
            and thisModel}.atomName == "N") {
 
             break
 
             break
 
         }
 
         }
    }
+
         else if ({(atomno=no) and (chain=iChain)
    return {(atomno=@{no+i}) and (chain=gChain)}.atomIndex
+
            and thisModel}.atomName == "CB") {
}
+
            no -= 3
 
 
function getOidx (BBidx) {
 
    var no = {atomIndex=BBidx}.atomno
 
    var i = 1
 
    for (; i < 4; i++) {
 
         if ({(atomno=@{no+i}) and (chain=gChain)}.atomName == "O") {
 
 
             break
 
             break
 
         }
 
         }
 
     }
 
     }
     return {(atomno=@{no+i}) and (chain=gChain)}.atomIndex
+
     return {(atomno=no) and (chain=iChain) and thisModel}.atomIndex
 
}
 
}
  
function getNwardBBno (iNo, iChain) {
+
function is_bb_idx(aIdx) {
     while ((iNo >= 0) and (
+
     var ret = false
        ({(atomno=iNo) and (chain=iChain)}.atomName != "N")
+
    switch({atomIndex=aIdx}.atomName) {
        and ({(atomno=iNo) and (chain=iChain)}.atomName != "C")
+
    case "N":
         and ({(atomno=iNo) and (chain=iChain)}.atomName != "CA"))) {
+
    case "CA":
         iNo--
+
    case "C":
 +
         ret = true
 +
         break
 
     }
 
     }
     return iNo
+
     return ret
 
}
 
}
  
function getNwardBBidx (idx, iChain) {
+
function is_sc_idx(aIdx) {
    var no = {atomIndex=idx}.atomno - 1
 
    no = getNwardBBno( no, iChain)
 
    return ((no >= 0) ? ({(atomno=no) and (chain=iChain)}.atomIndex) : -1)
 
}
 
  
function getCwardBBno (iNo, iChain) {
+
     var ret = false
     while ((iNo < gMaxNo) and (
+
    if (not is_bb_idx(aIDx)) {
        ({(atomno=iNo) and (chain=iChain)}.atomName != "N")
 
        and ({(atomno=iNo) and (chain=iChain)}.atomName != "C")
 
        and ({(atomno=iNo) and (chain=iChain)}.atomName != "CA"))) {
 
        iNo++
 
    }
 
    return iNo
 
}
 
  
function getCwardBBidx (idx, iChain) {
+
        ret = true
    var no = {atomIndex=idx}.atomno + 1
+
        switch({atomIndex=aIdx}.atomName) {
     no = getCwardBBno( no, iChain)
+
        case "O":
     return ((no >= 0) ? ({(atomno=no) and (chain=iChain)}.atomIndex) : -1)
+
        case "CB":
 +
            ret = false
 +
            break
 +
        }
 +
     }
 +
     return ret
 
}
 
}
  
function getScSet (scIdx, iChain) {
+
function select_add_sc(fromIdx) {
     var scSet = ({})
+
     var iNo = {atomIndex=fromIdx}.atomno
     var idx = getScBBidx(scIdx, iChain)
+
     var iChain = {atomIndex=fromIdx}.chain
    var iNo = {atomIndex=idx}.atomno + 3
+
     select none
      
+
     while ({(atomno=iNo) and (chain=iChain)
     for (var i = 1; i < 20; i++) {
+
        and thisModel and sidechain}) {
         idx = {(atomno=@{iNo+i}) and (chain=iChain)}.atomIndex
+
         var a = {(atomno=iNo) and (chain=iChain)and thisModel}
         if (isBBidx(idx)) {
+
         a.selected = true
            break
+
         iNo++
         }
 
        scSet = scSet or {atomIndex=idx}
 
 
     }
 
     }
    return scSet
 
 
}
 
}
  
function getScBBidx (idx, iChain) {
+
# Resolve collisions on selection
    var no = {atomIndex=idx}.atomno
+
function handle_collisions( targetIdx) {
    for (; no > 0; no--) {
 
        if ({(atomno=no) and (chain=iChain)}.atomName == "CA") {
 
            break
 
        }
 
        else if ({(atomno=no) and (chain=iChain)}.atomName == "C") {
 
            break
 
        }
 
        else if ({(atomno=no) and (chain=iChain)}.atomName == "N") {
 
            break
 
        }
 
        else if ({(atomno=no) and (chain=iChain)}.atomName == "CB") {
 
            no -= 3
 
            break
 
        }
 
    }
 
    return {(atomno=no) and (chain=iChain)}.atomIndex
 
}
 
  
function isBBidx(aIdx) {
+
    # For all selected atoms
     var ret = FALSE
+
     for (var iNo = {selected}.min.atomno; iNo <= {selected}.max.atomno; iNo++) {
    switch({atomIndex=aIdx}.atomName) {
+
         var idx = {(atomno=iNo) and (chain=gchain)
    case "N":
+
            and thisModel}.atomIndex
    case "CA":
+
        if ({atomindex=idx}.selected) {
    case "C":
 
         ret = TRUE
 
        break
 
    }
 
    return ret
 
}
 
  
function isSCidx(aIdx) {
+
            # Collect local colliders
 
+
            var lcAtoms = (within(kCtolerance, false, {atomIndex=idx})
    var ret = FALSE
+
                and not {atomIndex=idx}
    if (not isBBidx(aIDx)) {
+
                and not {gOkCollide}
 
+
                and not connected({atomIndex=idx}))
        ret = TRUE
+
            if (lcAtoms) {
        switch({atomIndex=aIdx}.atomName) {
+
                # Ignore kinked BB
        case "O":
+
                try {
        case "CB":
+
                    if (is_bb_idx(idx) and (angle(
            ret = FALSE
+
                        {atomIndex=@{get_cward_bb_idx(idx, gChain)}}, {atomIndex=idx},
            break
+
                        {atomIndex=@{get_nward_bb_idx(idx, gChain)}}) < 100.0)) {
        }
+
                        continue
    }
+
                    }
    return ret
+
                }
}
+
                catch {
 +
                }
  
function addSideChainToSelection(CAno, isAdd, addOXT, iChain) {
+
                # For all local colliders
    var iNo = CAno+3
+
                for (var c = 1; c <= lcAtoms.size; c++ ) {
    while ({(atomno=iNo) and (chain=iChain)}.resno == {(atomno=CAno) and (chain=iChain)}.resno) {
+
                    var cidx = lcAtoms[c].atomIndex
        {(atomno=iNo) and (chain=iChain)}.selected = isAdd
 
        if ({(atomno=iNo) and (chain=iChain)}.atomName == "OXT") {
 
            {(atomno=iNo) and (chain=iChain)}.selected = addOXT
 
        }
 
        iNo++
 
    }
 
}
 
  
function selectAddSideChain(fromIdx) {
+
                    # If it is with water, delete it
    var iNo = {atomIndex=fromIdx}.atomno
+
                    if (lcAtoms[c].group = "HOH") {
    var iChain = {atomIndex=fromIdx}.chain
+
                        delete {atomIndex=cidx}
    select none
+
                    }
    while ({(atomno=iNo) and (chain=iChain)}.atomName != "N") {
 
        {(atomno=iNo) and (chain=iChain)}.selected = TRUE
 
        iNo++
 
        if (iNo > {chain=iChain}.atomno.max) {
 
            break
 
        }
 
    }
 
}
 
  
# First and last are BB atoms
+
                    # else if it is with side chain not proline, fix it
# Any side atoms in the range are also selected
+
                    else if (is_sc_idx(cidx) and ({atomIndex=cidx}.group != "PRO")) {
function selectNwardIdx (firstIdx, lastIdx) {
+
                        fix_sc_collision_2(cidx)
    var firstno = ((firstIdx < 0) ? {atomIndex=lastIdx}.atomno : {atomIndex=firstIdx}.atomno)
+
                       
    var lastno = ((lastIdx < 0) ? firstno : {atomIndex=lastIdx}.atomno)
+
                        # If not fixed, exit fail
    var iChain = ((firstIdx < 0)
+
                        if (not gOk2) {
        ? {atomIndex=lastIdx}.chain : {atomIndex=firstIdx}.chain)
+
                            return # early exit (break n jmol bug)
 +
                        }
 +
                    }
  
    select (atomno <= firstno) and (atomno >= lastno) and (chain = iChain)
+
                    # else if it is itself a side chain not proline, fix it
 +
                    else if (is_sc_idx(idx) and ({atomIndex=idx}.group != "PRO")) {
 +
                        fix_sc_collision_2(idx)
  
    if ({(atomno=firstno) and (chain=gChain)}.atomName == "C") { # if psi
+
                        # If not fixed, exit fail
        addSideChainToSelection(firstno-1, TRUE, TRUE, iChain)
+
                        if (not gOk2) {
        {(atomno=@{firstno+1}) and (chain=iChain)}.selected = TRUE # add O
+
                            return # early exit (break n jmol bug)
    }
+
                        }
    if ({(atomno=firstno) and (chain=iChain)}.atomName == "CA") {
+
                    }
        addSideChainToSelection(firstno, TRUE, FALSE, iChain)
 
    }
 
    if ({(atomno=lastno) and (chain=iChain)}.atomName == "C") { # if psi
 
        addSideChainToSelection(lastno-1, FALSE, FALSE, iChain)
 
    }
 
}
 
  
# First and last are BB atoms
+
                    # Else if it is with O, counter-rotate
# Any side atoms in the range are also selected
+
                    else if (lcAtoms[c].atomName = "O") {
function selectCwardIdx (firstIdx, lastIdx) {
+
                        counter_rotate_2(lcAtoms[c].atomIndex,
    var firstno = ((firstIdx < 0) ? gMaxNo : {atomIndex=firstIdx}.atomno)
+
                            {atomIndex=idx}.xyz, targetIdx, false)
    var lastno = ((lastIdx < 0) ? 1 : {atomIndex=lastIdx}.atomno)
 
    var iChain = ((firstIdx < 0)
 
        ? {atomIndex=lastIdx}.chain : {atomIndex=firstIdx}.chain)
 
  
    # If nWard anchor in range, begin selection with it
+
                        # If not fixed, exit fail
    if ((gNanchorIdx >= 0) and ({atomIndex=gNanchorIdx}.chain == iChain))  {
+
                        if (not gOk2) {
        var aNo = {atomIndex=gNanchorIdx}.atomno
+
                            return # early exit (break n jmol bug)
        if (aNo > firstNo) {
+
                        }
            firstno = aNo
+
                    }
        }
 
    }
 
  
    # If cWard anchor in range, end selection with it
+
                    # Else if it is itself O, counter-rotate
    if ((gCanchorIdx >= 0) and ({atomIndex=gCanchorIdx}.chain == iChain)) {
+
                    else if ({atomIndex=idx}.atomName = "O") {
        var aNo = {atomIndex=gCanchorIdx}.atomno
+
                        counter_rotate_2(idx, lcAtoms[c].xyz, targetIdx, false)
        if (aNo < lastNo) {
 
            lastno = aNo
 
        }
 
    }
 
  
    select (atomno >= firstno) and (atomno <= lastno) and (chain = iChain)
+
                        # If not fixed, exit fail
 +
                        if (not gOk2) {
 +
                            return # early exit (break n jmol bug)
 +
                        }
 +
                    }
  
    if ({(atomno=firstno) and (chain=iChain)}.atomName == "C") { # if psi
+
                    else {   # Else not fixed, exit fail
        addSideChainToSelection(firstno-1, FALSE, FALSE, iChain)
+
                        gOk2 = false
    }
+
                        return # early exit (break n jmol bug)
    if ({(atomno=lastno) and (chain=iChain)}.atomName == "CA") {
+
                    }
        addSideChainToSelection(lastno, TRUE, FALSE, iChain)
+
                } # endfor
    }
+
            }
    if ({(atomno=lastno) and (chain=iChain)}.atomName == "C") { # if psi
+
         }
        addSideChainToSelection(lastno-1, TRUE, TRUE, iChain)
+
     } # endfor iNo
         {(atomno=@{lastno+1}) and (chain=iChain)}.selected = TRUE # add O
 
     }
 
 
}
 
}
  
# Selected must include second parameter but not the first
+
# Rotate rotor set to move target atom to its proper place
function setDistanceIdx (staticIdx, mobileIdx, desired) {
+
function tug_track_idx(targetIdx, targetPt, nWard, cDetect) {
     try {
+
     gOK = false
        var v = {atomIndex=mobileIdx}.xyz - {atomIndex=staticIdx}.xyz
+
    var pt = targetPt
        var dist = distance({atomIndex=staticIdx}, {atomIndex=mobileIdx})
+
    var dist = distance(pt, {atomIndex=targetIdx}.xyz)
        translateSelected @{((v * (desired/dist)) - v)}
 
    }
 
    catch {
 
    }
 
}
 
  
 +
    var rotors = (nWard ? gNrotors : gCrotors)
  
# Selected must include third parameter but not the first
+
    # For a number of passes
function setAngleIdx (statorIdx, pivotIdx, rotorIdx, toangle) {
+
    for (var pass1 = 0; pass1 < 20; pass1++) {
    try {
+
         var blocked = ({})
        var v1={atomIndex=statorIdx}.xyz - {atomIndex=pivotIdx}.xyz
+
         for (var pass2 = 0; pass2 < (rotors.size/4); pass2++) {
         var v2={atomIndex=rotorIdx}.xyz - {atomIndex=pivotIdx}.xyz
 
         var axis = cross(v1, v2) + {atomIndex=pivotIdx}.xyz
 
        var curangle =  angle({atomIndex=statorIdx},
 
            {atomIndex=pivotIdx}, {atomIndex=rotorIdx})
 
        rotateselected @axis {atomIndex = pivotIdx} @{curangle-toangle}
 
    }
 
    catch {
 
    }
 
}
 
  
# Selected must include fourth parameter but not the first
+
            var v1 = {atomIndex=targetIdx}.xyz - pt
function setDihedralIdx (stator1idx, stator2idx, rotor1idx, rotor2idx, toangle) {
 
    try {
 
        var a1={atomIndex = stator1idx}.xyz
 
        var a2={atomIndex = stator2idx}.xyz
 
        var a3={atomIndex = rotor1idx}.xyz
 
        var a4={atomIndex = rotor2idx}.xyz
 
        var curangle =  angle(a1, a2, a3, a4)
 
        rotateselected {a3} {a2} @{curangle-toangle}
 
    }
 
    catch {
 
    }
 
}
 
  
function countCollisions(rc) {
+
            # Find the most orthgonal unused rotor
    var cAtoms = ({})
+
            var imax = 0
    for (var idx = {*}.min.atomIndex; idx <= {*}.max.atomIndex; idx++) {
+
            var smax = 0.5
        if ({atomIndex=idx}.size > 0) {
+
            for (var i = 1; i < rotors.size; i += 4) {
            var lcAtoms = (within(kCtolerance, FALSE, {atomIndex=idx})
+
                var i2 = rotors[i+1]
                and {atomIndex > idx}
+
                var i3 = rotors[i+2]
                and not {rc}
+
                var i4 = rotors[i+3]
                and not connected({atomIndex=idx}))
+
                if ((i2 != targetIdx) and (i3 != targetIdx) and (i4 != targetIdx)) {
            if (lcAtoms.size > 0) {
+
                    if ({blocked and {atomIndex=i2}}.count == 0) {
                cAtoms = cAtoms or lcAtoms
+
                        var v2 = {atomIndex=i3}.xyz - {atomIndex=i2}.xyz
            }
 
        }
 
    }
 
    return cAtoms.size
 
}
 
 
 
# Resolve collisions
 
function handleCollisions2( targetIdx) {
 
  
    # For all selected atoms
+
                        var s = sin(abs(angle(v1, {0 0 0}, v2)))
    for (var iNo = {selected}.min.atomno; iNo <= {selected}.max.atomno; iNo++) {
+
                        if (s > smax) {
        var idx = {(atomno=iNo) and (chain=gchain)}.atomIndex
+
                            smax = s
        if ({atomindex=idx}.selected) {
+
                            imax = i
 
+
                        }
            # Collect local colliders
+
                     }
            var lcAtoms = (within(kCtolerance, FALSE, {atomIndex=idx})
 
                and not {atomIndex=idx}
 
                and not {gOkCollide}
 
                and not connected({atomIndex=idx}))
 
            if (lcAtoms.size > 0) {
 
 
 
                # Ignore kinked BB
 
                if (isBBidx(idx) and angle({atomIndex=@{getCwardBBidx(idx, gChain)}},
 
                     {atomIndex=idx} , {atomIndex=@{getNwardBBidx(idx, gChain)}}) < 100) {
 
                    continue
 
 
                 }
 
                 }
 +
            }
  
                # For all local colliders
+
            # If no more rotors, break to next full try
                for (var c = 1; c <= lcAtoms.size; c++ ) {
+
            if (imax == 0) {
                    var cidx = lcAtoms[c].atomIndex
+
              break
 +
            }
 +
            var i1 = rotors[imax+0]
 +
            var i2 = rotors[imax+1]
 +
            var i3 = rotors[imax+2]
 +
            var i4 = rotors[imax+3]
  
                    # If it is with water, delete it
+
            # Get dihedral of rotor with target point
                    if (lcAtoms[c].group = "HOH") {
+
            var dt = angle({atomIndex=targetIdx}, {atomIndex=i2}, {atomIndex=i3}, pt)
                        delete {atomIndex=cidx}
+
            var dh = angle({atomIndex=i1}, {atomIndex=i2}, {atomIndex=i3}, {atomIndex=i4})
                    }
+
            if (abs(dh).type == "string") {
 +
                dh = -50
 +
            }
 +
            var psi = dh + dt
 +
            var phi = dh + dt
  
                    # else if it is with side chain not proline, fix it
+
            # Compute resultant psi and phi
                    else if (isSCidx(cidx) and ({atomIndex=cidx}.group != "PRO")) {
+
            # and select from target atom to first half of rotor
                        fixSCcollision2(cidx)
+
            var movePt = false
                         recollect = TRUE
+
            if (nWard) {
 
+
                if ({atomIndex=i2}.atomName="CA") {
                        # If not fixed, exit fail
+
                    psi = angle({atomIndex=@{get_cward_bb_idx(i1, gChain)}}, {atomIndex=i1},
                         if (not gOk2) {
+
                         {atomIndex=i2}, {atomIndex=i3}) + dt
                            return # early exit (break n jmol bug)
+
                }
                        }
+
                else {
                    }
+
                    phi = angle({atomIndex=i1}, {atomIndex=i2},
 +
                         {atomIndex=i3}, {atomIndex=@{get_nward_bb_idx(i3, gChain)}}) + dt
 +
                }
  
                     # else if it is itself a side chain not proline, fix it
+
                if ({atomIndex=i2}.atomno > {atomIndex=targetIdx}.atomno) {
                    else if (isSCidx(idx) and ({atomIndex=idx}.group != "PRO")) {
+
                     movePt = true
                         fixSCcollision2(idx)
+
                    select_nward_idx(i3, get_cward_bb_idx(targetIdx, gChain))
                         recollect = TRUE
+
                    {atomIndex=targetIdx}.selected = true
 +
                }
 +
                else {
 +
                    select_cward_idx(i2, targetIdx)
 +
                }
 +
            }
 +
            else {
 +
                if (({atomIndex=i2}.atomName="CA")) {
 +
                    phi = angle({atomIndex=@{get_nward_bb_idx(i1, gChain)}}, {atomIndex=i1},
 +
                         {atomIndex=i2}, {atomIndex=i3}) + dt
 +
                }
 +
                else {
 +
                    psi = angle({atomIndex=i2}, {atomIndex=i3},
 +
                         {atomIndex=i4}, {atomIndex=@{get_cward_bb_idx(i4, gChain)}}) + dt
 +
                }
  
                        # If not fixed, exit fail
+
                if ({atomIndex=i2}.atomno < {atomIndex=targetIdx}.atomno) {
                        if (not gOk2) {
+
                    movePt = true
                            return # early exit (break n jmol bug)
+
                    select_cward_idx(i3, get_nward_bb_idx(targetIdx, gChain))
                        }
+
                    {atomIndex=targetIdx}.selected = true
                     }
+
                }
 +
                else {
 +
                     select_nward_idx(i2, targetIdx)
 +
                }
 +
            }
  
                    # Else if it is with O, counter-rotate
+
            # Relax rules if desperate
                    else if (lcAtoms[c].atomName = "O") {
+
            if (pass1 > 10) {
                        counterRotate2(lcAtoms[c].atomIndex,
+
                phi = -50
                            {atomIndex=idx}.xyz, targetIdx, FALSE)
+
            }
  
                        # If not fixed, exit fail
+
            # If rotation within ramachandran limits
                        if (not gOk2) {
+
            if ((abs(dt) >= 0.1) and
                            return # early exit (break n jmol bug)
+
                (({atomIndex=i2}.group=="GLY") or (phi < 0))) {
                        }
 
                    }
 
  
                    # Else if it is itself O, counter-rotate
+
                # If moving target point, put the target atom there
                    else if ({atomIndex=idx}.atomName = "O") {
+
                var cp = {atomIndex=targetIdx}.xyz
                        counterRotate2(idx, lcAtoms[c].xyz, targetIdx, FALSE)
+
                if (movePt) {
 +
                    dt = -dt
 +
                    {atomIndex=targetIdx}.xyz = pt
 +
                }
  
                        # If not fixed, exit fail
+
                # Rotate to minimize vector ====================
 +
                rotateSelected {atomIndex=i2} {atomIndex=i3} @dt
 +
 
 +
                # If collision checking
 +
                if (cDetect) {
 +
 
 +
                    # If collision, back off by eighths
 +
                    var wasCollision = false
 +
                    for (var ci = 0; ci < 4; ci++) {
 +
                        if (ci < 3) {
 +
                            dt /= 2
 +
                        }
 +
                        handle_collisions( targetIdx)
 
                         if (not gOk2) {
 
                         if (not gOk2) {
                             return # early exit (break n jmol bug)
+
                             wasCollision = true
 +
                            rotateSelected {atomIndex=i2} {atomIndex=i3} @{-dt}
 +
                        }
 +
                        else if (wasCollision) {
 +
                            if (ci <3) {
 +
                                rotateSelected {atomIndex=i2} {atomIndex=i3} @{dt}
 +
                            }
 +
                        }
 +
                        else {
 +
                            break
 
                         }
 
                         }
                    }
 
  
                    else {    # Else not fixed, exit fail
+
                         if (dt < 0.01) {
                         gOk2 = FALSE
+
                            break
                        return # early exit (break n jmol bug)
+
                        }
                    }
+
                    } # endfor
                } # endfor
+
                }
            }
 
        }
 
    } # endfor iNo
 
}
 
  
# Rotate rotor set to move target atom to its proper place
+
                # If moving target point, put the target atom back
function tugTrackIdx(targetIdx, targetPt, nWard, cDetect) {
+
                if (movePt) {
    gOK = FALSE
+
                    pt = {atomIndex=targetIdx}.xyz
    var pt = targetPt
+
                    {atomIndex=targetIdx}.xyz = cp
    var dist = distance(pt, {atomIndex=targetIdx}.xyz)
+
                }
  
    var rotors = (nWard ? gNrotors : gCrotors)
+
            }
  
    # For a number of passes
+
            # If close enough, stop
    for (var pass1 = 0; pass1 < 20; pass1++) {
+
            if (distance(pt, {atomIndex=targetIdx}) < kDtolerance) {
        var blocked = ({})
+
                gOK = true
        for (var pass2 = 0; pass2 < (rotors.size/4); pass2++) {
+
                gTargetPt = pt
 +
                break
 +
            }
  
             var v1 = {atomIndex=targetIdx}.xyz - pt
+
             # Block rotor
 +
            blocked |= {atomIndex=i2}
  
            # Find the most orthgonal unused rotor
+
        }  # endfor num rotors passes
            var imax = 0
 
            var smax = 0.5
 
            for (var i = 1; i < rotors.size; i += 4) {
 
                var i2 = rotors[i+1]
 
                var i3 = rotors[i+2]
 
                var i4 = rotors[i+3]
 
                if ((i2 != targetIdx) and (i3 != targetIdx) and (i4 != targetIdx)) {
 
                    if ({blocked and {atomIndex=i2}}.count == 0) {
 
                        var v2 = {atomIndex=i3}.xyz - {atomIndex=i2}.xyz
 
  
                        var s = sin(abs(angle(v1, {0 0 0}, v2)))
+
        if (gOK) {
                        if (s > smax) {
+
            break
                            smax = s
+
        }
                            imax = i
+
    }   # endfor 20 passes
                        }
+
}
                    }
 
                }
 
            }
 
  
            # If no more rotors, break to next full try
+
# Counter rotate bonds on either side of a BB O
            if (imax == 0) {
+
function do_counter_rotate(caPhiIdx, nIdx, cIdx, oIdx, caPsiIdx, dir, nWard) {
              break
 
            }
 
            var i1 = rotors[imax+0]
 
            var i2 = rotors[imax+1]
 
            var i3 = rotors[imax+2]
 
            var i4 = rotors[imax+3]
 
  
            # Get dihedral of rotor with target point
+
    # Rotate psi
            var dt = angle({atomIndex=targetIdx}, {atomIndex=i2}, {atomIndex=i3}, pt)
+
    {atomIndex=nIdx}.selected = nWard
            var dh = angle({atomIndex=i1}, {atomIndex=i2}, {atomIndex=i3}, {atomIndex=i4})
+
    {atomIndex=cIdx}.selected = nWard
            if (dh == "NaN") {
+
    {atomIndex=oIdx}.selected = nward
                dh = -50
+
    rotateSelected {atomIndex=caPsiIdx} {atomIndex=cIdx} @{dir}
            }
 
            var psi = dh + dt
 
            var phi = dh + dt
 
  
            # Compute resultant psi and phi
+
    # Counter-rotate phi
            #  and select from target atom to first half of rotor
+
    {atomIndex=nIdx}.selected = not nWard
            var movePt = FALSE
+
    {atomIndex=cIdx}.selected = not nWard
            if (nWard) {
+
    {atomIndex=oIdx}.selected = not nward
                if ({atomIndex=i2}.atomName="CA") {
+
    rotateSelected {atomIndex=nIdx} {atomIndex=caPhiIdx} @{-dir}
                    psi = angle({atomIndex=@{getCwardBBidx(i1, gChain)}}, {atomIndex=i1},
+
}
                        {atomIndex=i2}, {atomIndex=i3}) + dt
 
                }
 
                else {
 
                    phi = angle({atomIndex=i1}, {atomIndex=i2},
 
                        {atomIndex=i3}, {atomIndex=@{getNwardBBidx(i3, gChain)}}) + dt
 
                }
 
  
                if ({atomIndex=i2}.atomno > {atomIndex=targetIdx}.atomno) {
+
function counter_rotate(oIdx, dir, nWard) {
                    movePt = TRUE
 
                    selectNwardIdx(i3, getCwardBBidx(targetIdx, gChain))
 
                    {atomIndex=targetIdx}.selected = TRUE
 
                }
 
                else {
 
                    selectCwardIdx(i2, targetIdx)
 
                }
 
            }
 
            else {
 
                if (({atomIndex=i2}.atomName="CA")) {
 
                    phi = angle({atomIndex=@{getNwardBBidx(i1, gChain)}}, {atomIndex=i1},
 
                        {atomIndex=i2}, {atomIndex=i3}) + dt
 
                }
 
                else {
 
                    psi = angle({atomIndex=i2}, {atomIndex=i3},
 
                        {atomIndex=i4}, {atomIndex=@{getCwardBBidx(i4, gChain)}}) + dt
 
                }
 
  
                if ({atomIndex=i2}.atomno < {atomIndex=targetIdx}.atomno) {
+
    var iChain = {atomIndex=oIdx}.chain
                    movePt = TRUE
+
    var selsave = {selected}
                    selectCwardIdx(i3, getNwardBBidx(targetIdx, gChain))
+
    var cIdx = get_sc_bb_idx(oIdx, iChain)
                    {atomIndex=targetIdx}.selected = TRUE
+
    var nIdx = get_cward_bb_idx(cIdx, iChain)
                }
+
    var caPhiIdx = get_cward_bb_idx(nIdx, iChain)
                else {
+
    var caPsiIdx = get_nward_bb_idx(cIdx, iChain)
                    selectNwardIdx(i2, targetIdx)
+
 
                }
+
    if (nWard) {
            }
+
        var nNo = {(chain=iChain) and thisModel}.atomno.min
 +
        select_nward_idx(caPsiIdx, {(atomno=nNo) and (chain=iChain)
 +
            and thisModel}.atomIndex)
 +
    }
 +
    else {
 +
        var cNo = {(chain=iChain) and thisModel}.atomno.max
 +
        select_cward_idx(caPhiIdx, {(atomno=cNo) and (chain=iChain)
 +
            and thisModel}.atomIndex)
 +
    }
  
            # Relax rules if desperate
+
    # Counter-rotate
            if (pass1 > 10) {
+
    do_counter_rotate(caPhiIdx, nIdx, cIdx, oIdx, caPsiIdx, dir, not nWard)
                phi = -50
+
    select selsave
            }
+
}
  
            # If rotation within ramachandran limits
+
function counter_rotate_2(oIdx, toPt, terminalIdx, oDrag) {
            if ((abs(dt) >= 0.1) and
 
                (({atomIndex=i2}.group=="GLY") or (phi < 0))) {
 
  
                # If moving target point, put the target atom there
+
    var iChain = {atomIndex=oIdx}.chain
                var cp = {atomIndex=targetIdx}.xyz
+
    var selsave = {selected}
                if (movePt) {
+
    var gOk2 = true
                    dt = -dt
+
    var cIdx = get_sc_bb_idx(oIdx, iChain)
                    {atomIndex=targetIdx}.xyz = pt
+
    var nIdx = get_cward_bb_idx(cIdx, iChain)
                }
+
    var caPhiIdx = get_cward_bb_idx(nIdx, iChain)
 +
    var caPsiIdx = get_nward_bb_idx(cIdx, iChain)
 +
 
 +
    var nTward = ({atomIndex=oIdx}.atomno < {atomIndex=terminalIdx}.atomno)
 +
    if (nTward) {
 +
        select_cward_idx(cIdx, terminalIdx)
 +
    }
 +
    else {
 +
        select_nward_idx(nIdx, terminalIdx)
 +
    }
  
                # Rotate to minimize vector ====================
+
    # Until all collisions cancelled
                rotateSelected {atomIndex=i2} {atomIndex=i3} @dt
+
    var dir = 5
 +
    var ang = angle(toPt, {atomIndex=oIdx}, {atomIndex=cIdx})
 +
    var tcount = 0
 +
    while (oDrag or (within(kCtolerance, false, {atomIndex=oIdx})
 +
            and not {atomIndex=oIdx} and not connected({atomIndex=oIdx})
 +
            and not {gOkCollide} > 0)) {
  
                # If collision checking
+
        # Counter-rotate
                if (cDetect) {
+
        do_counter_rotate(caPhiIdx, nIdx, cIdx, oIdx, caPsiIdx, dir, nTward)
 +
        var newang = angle(toPt, {atomIndex=oIdx}, {atomIndex=cIdx})
  
                    # If collision, back off by eighths
+
        # If wrong direction once, undo and reverse
                    var wasCollision = FALSE
+
        if (newang > ang) {
                    for (var ci = 0; ci < 4; ci++) {
+
            do_counter_rotate(caPhiIDx, nIdx, cIdx, oIdx, caPsiIdx, -dir, nTward)
                        if (ci < 3) {
 
                            dt /= 2
 
                        }
 
                        handleCollisions2( targetIdx)
 
                        if (not gOk2) {
 
                            wasCollision = TRUE
 
                            rotateSelected {atomIndex=i2} {atomIndex=i3} @{-dt}
 
                        }
 
                        else if (wasCollision) {
 
                            if (ci <3) {
 
                                rotateSelected {atomIndex=i2} {atomIndex=i3} @{dt}
 
                            }
 
                        }
 
                        else {
 
                            break
 
                        }
 
  
                        if (dt < 0.01) {
+
            # If first time, continue in opposite direction
                            break
+
            dir *= -1
                        }
+
            if (dir < 0) {
                    } # endfor
+
                continue
                }
+
            }
 +
        }
  
                # If moving target point, put the target atom back
+
        if (oDrag) {
                if (movePt) {
+
            break
                    pt = {atomIndex=targetIdx}.xyz
+
        }
                    {atomIndex=targetIdx}.xyz = cp
 
                }
 
  
             }
+
        # If no go, undo and exit
 +
        tcount++
 +
        if (tcount > (360/abs(dir))) {
 +
             gOk2 = false
 +
            break
 +
        }
  
            # If close enough, stop
+
    } # endwhile
            if (distance(pt, {atomIndex=targetIdx}) < kDtolerance) {
+
    select selsave
                gOK = TRUE
+
}
                gTargetPt = pt
 
                break
 
            }
 
  
            # Block rotor
+
# Repair proline
            blocked |= {atomIndex=i2}
+
function repair_proline(BBidx) {
 +
    var cbidx = get_cb_idx(BBidx)
 +
    var cbno = {atomIndex=cbidx}.atomno
 +
    var cgidx = {(atomno=@{cbno+1}) and (chain=gChain)
 +
        and thisModel}.atomIndex
 +
    var cdidx = {(atomno=@{cbno+2}) and (chain=gChain)
 +
        and thisModel}.atomIndex
 +
    var caidx = {(atomno=@{cbno-3}) and (chain=gChain)
 +
        and thisModel}.atomIndex
 +
    var nidx = {(atomno=@{cbno-4}) and (chain=gChain)
 +
        and thisModel}.atomIndex
  
        }   # endfor num rotors passes
+
    select {atomIndex=cbidx}
 +
    set_angle_idx(nidx, caidx, cbidx, 109.5)
  
        if (gOK) {
+
    select {atomIndex=cdidx}
            break
+
    set_distance_idx(nidx, cdidx, 1.47)
        }
+
    set_angle_idx(caidx, nidx, cdidx, 102.7)
     }  # endfor 20 passes
+
     set_dihedral_idx(cbidx, caidx, nidx, cdidx, 16.2)
}
 
  
# Counter rotate bonds on either side of a BB O
+
     select {atomIndex=cgidx}
function docounterRotate(caPhiIdx, nIdx, cIdx, oIdx, caPsiIdx, dir, nWard) {
+
     set_distance_idx(cdidx, cgidx, 1.51)
 
+
     set_angle_idx(nidx, cdidx, cgidx, 106.4)
    # Rotate psi
+
     set_dihedral_idx(caidx, nidx, cdidx, cgidx, 16.2)
     {atomIndex=nIdx}.selected = nWard
 
     {atomIndex=cIdx}.selected = nWard
 
     {atomIndex=oIdx}.selected = nward
 
     rotateSelected {atomIndex=caPsiIdx} {atomIndex=cIdx} @{dir}
 
 
 
    # Counter-rotate phi
 
    {atomIndex=nIdx}.selected = not nWard
 
    {atomIndex=cIdx}.selected = not nWard
 
    {atomIndex=oIdx}.selected = not nward
 
    rotateSelected {atomIndex=nIdx} {atomIndex=caPhiIdx} @{-dir}
 
 
}
 
}
  
function counterRotate(oIdx, dir, nWard) {
+
# Repair side chain
 +
function repair_sc(targetIdx, nWard) {
  
     var iChain = {atomIndex=oIdx}.chain
+
     var idx = (nWard ? get_cward_bb_idx(targetIdx, gChain) : get_nward_bb_idx(targetIdx, gChain))
    var selsave = {selected}
 
    var cIdx = getScBBidx(oIdx, iChain)
 
    var nIdx = getCwardBBidx(cIdx, iChain)
 
    var caPhiIdx = getCwardBBidx(nIdx, iChain)
 
    var caPsiIdx = getNwardBBidx(cIdx, iChain)
 
  
     if (nWard) {
+
     if (({atomIndex=targetIdx}.atomName == "CA")
         nNo = {chain=iChain}.atomno.min
+
         and ({atomIndex=targetIdx}.group != "GLY")) {
         selectNwardIdx(caPsiIdx, {(atomno=nNo) and (chain=iChain)}.atomIndex)
+
        var cbidx = get_cb_idx(targetIdx)
    }
+
        select none
    else {
+
        select_add_sc(cbidx)
        cNo = {chain=iChain}.atomno.max
+
        set_angle_idx(idx, targetIdx, cbidx, 110.0)
        selectCwardIdx(caPhiIdx, {(atomno=cNo) and (chain=iChain)}.atomIndex)
+
         set_distance_idx(targetIdx, cbidx, 1.5)
 +
        if ({atomIndex=targetIdx}.group != "PRO") {
 +
            var colliders = (within(kCtolerance, false, {selected})
 +
                and not {atomIndex=targetIdx} and not {selected})
 +
            if (colliders) {
 +
                if ({atomIndex=targetIdx}.group != "ALA") {
 +
                    fix_sc_collision_2(cbidx)
 +
                }
 +
            }
 +
        }
 +
        else {
 +
            if (nWard) {
 +
            }
 +
            else {
 +
                set_dihedral_idx(get_nward_bb_idx(idx, gChain), idx, targetIdx, cbidx, 174.2)
 +
            }
 +
        }
 
     }
 
     }
  
     # Counter-rotate
+
     else if ({atomIndex=targetIdx}.atomName == "C") {
    docounterRotate(caPhiIdx, nIdx, cIdx, oIdx, caPsiIdx, dir, not nWard)
+
        var oidx = get_o_idx(targetIdx)
    select selsave
+
        select {atomIndex=oidx}
}
+
        set_angle_idx(idx, targetIdx, oidx, 120.0)
 
+
        set_distance_idx(targetIdx, oidx, 1.21)
function counterRotate2(oIdx, toPt, terminalIdx, oDrag) {
+
        if (nWard) {
 
+
            set_dihedral_idx(get_cward_bb_idx(idx, gChain), idx, targetIdx, oidx, 0.0)
    var iChain = {atomIndex=oIdx}.chain
+
        }
    var selsave = {selected}
+
        if ({atomIndex=idx}.group == "PRO") {
    var gOk2 = TRUE
+
            repair_proline(idx)
    var cIdx = getScBBidx(oIdx, iChain)
+
            var dNo = {atomIndex=targetIdx}.atomno + 4
    var nIdx = getCwardBBidx(cIdx, iChain)
+
            var dIdx = {(atomno=dNO) and (chain=gChain)
    var caPhiIdx = getCwardBBidx(nIdx, iChain)
+
                and thisModel}.atomIndex
    var caPsiIdx = getNwardBBidx(cIdx, iChain)
+
            var colliders = (within(kCtolerance, false, {atomIndex=dIdx})
 
+
                and not connected({atomIndex=dIdx})
    var nTward = ({atomIndex=oIdx}.atomno < {atomIndex=terminalIdx}.atomno)
+
                and not {atomIndex=dIdx})
    if (nTward) {
+
            for (var i = 1; i <= colliders.size; i++) {
        selectCwardIdx(cIdx, terminalIdx)
+
                if (colliders[i].atomName == "O") {
    }
+
                    counter_rotate_2(colliders[i].atomIndex,
    else {
+
                        {atomIndex=dIdx}.xyz, targetIdx, false)
         selectNwardIdx(nIdx, terminalIdx)
+
                }
 +
            }
 +
         }
 
     }
 
     }
 +
}
  
    # Until all collisions cancelled
+
# Rebuild Cward rotors set
    var dir = 5
+
function tug_track_c() {
    var ang = angle(toPt, {atomIndex=oIdx}, {atomIndex=cIdx})
 
    var tcount = 0
 
    while (oDrag or (within(kCtolerance, FALSE, {atomIndex=oIdx})
 
            and not {atomIndex=oIdx} and not connected({atomIndex=oIdx})
 
            and not {gOkCollide} > 0)) {
 
  
        # Counter-rotate
+
    # For all bb atoms cWard of cargo
        docounterRotate(caPhiIdx, nIdx, cIdx, oIdx, caPsiIdx, dir, nTward)
+
    var targetIdx = gCcargoIdx
        var newang = angle(toPt, {atomIndex=oIdx}, {atomIndex=cIdx})
+
    var okCount = 0
  
        # If wrong direction once, undo and reverse
+
    # Allow collisions with cargo
        if (newang > ang) {
+
    gOkCollide = gCargoSet
            docounterRotate(caPhiIDx, nIdx, cIdx, oIdx, caPsiIdx, -dir, nTward)
+
    var tcount = 0
 +
    while (targetIdx != gNanchorIdx) {
  
            # If first time, continue in opposite direction
+
        # Step to next atom
            dir *= -1
+
        targetIdx = get_cward_bb_idx(targetIdx, gChain)
            if (dir < 0) {
+
        if (targetIdx < 0) {
                continue
+
             break
             }
 
 
         }
 
         }
  
         if (oDrag) {
+
        # No collision with cargo allowed after two atoms placed
            break
+
         if (tcount == 2) {
 +
          gOkCollide = ({})
 
         }
 
         }
 
        # If no go, undo and exit
 
 
         tcount++
 
         tcount++
        if (tcount > (360/abs(dir))) {
 
            gOk2 = FALSE
 
            break
 
        }
 
  
     } # endwhile
+
        # Compute targets desired coords
     select selsave
+
        try {
}
+
            var c1idx = get_cward_bb_idx(targetIdx, gChain)
 +
            var n1idx = get_nward_bb_idx(targetIdx, gChain )
 +
            var n2idx = get_nward_bb_idx(n1Idx, gChain)
 +
            var n3idx = get_nward_bb_idx(n2Idx, gChain)
 +
            var pt = {0 0 0}
 +
            if ({atomIndex=targetIdx}.atomName == "N") {
 +
                var oidx = get_o_idx(n1idx)
 +
                select {atomIndex=oidx}
 +
      
 +
                # Desired target location is trigonal O
 +
                set_distance_idx(n1idx, oidx, 1.5)
 +
                pt = get_trigonal_idx(n2idx, n1idx, oidx, 1.37)
 +
                set_distance_idx(n1idx, oidx, 1.21)
 +
            }
 +
            else if (({atomIndex=targetIdx}.atomName == "C")
 +
                and ({atomIndex=targetIdx}.group != "GLY")) {
 +
   
 +
                # Desired target location is tetragonal CB
 +
                var cbidx = get_cb_idx(n1idx)
 +
                pt = get_tet_idx(n2idx, n1idx, cbidx, 1.5)
 +
            }
 +
            else { # CA (or GLY C)
 +
   
 +
                # Save current target coords
 +
                var cp = {atomIndex=targetIdx}.xyz
 +
      
 +
                # Set target atom at desired distance and angle
 +
                select {atomIndex=targetIdx}
 +
                set_distance_idx(n1idx, targetIdx, 1.5)
 +
                set_angle_idx(n2idx, n1idx, targetIdx, 120.0)
 +
                if ({atomIndex=targetIdx}.atomName == "CA") {
 +
                    set_dihedral_idx(n3idx, n2idx, n1idx, targetIdx, 180)
 +
                }
 +
   
 +
                # Record and restore target
 +
                pt = {atomIndex=targetIdx}.xyz
 +
                {atomIndex=targetIdx}.xyz = cp
 +
            }
 +
   
 +
            # If target not at desired location
 +
            if (distance(pt, {atomIndex=targetIdx}) > kDtolerance) {
 +
                okCount = 0
 +
                gTargetPt = pt
 +
                var xcount = 0
 +
                gOK = false
 +
                while ((xcount < 20) and (not gOK)) {
 +
   
 +
                    # Rotate on cWard rotor set to move it there
 +
                    tug_track_idx(targetIdx, pt, false, false)
 +
                    xcount++
 +
                }
 +
            }
 +
            else {
 +
                gOK = true
 +
                okCount++
 +
            }
 +
        }
 +
        catch {
 +
        }
  
# Repair proline
+
        # If successful
function repairProline(BBidx) {
+
        if (gOK == true) {
    var cbidx = getCBidx(BBidx)
 
    var cbno = {atomIndex=cbidx}.atomno
 
    var cgidx = {(atomno=@{cbno+1}) and (chain=gChain)}.atomIndex
 
    var cdidx = {(atomno=@{cbno+2}) and (chain=gChain)}.atomIndex
 
    var caidx = {(atomno=@{cbno-3}) and (chain=gChain)}.atomIndex
 
    var nidx = {(atomno=@{cbno-4}) and (chain=gChain)}.atomIndex
 
  
    select {atomIndex=cbidx}
+
            # Adust any side atoms
    setAngleIdx(nidx, caidx, cbidx, 109.5)
+
            repair_sc(targetIdx, false)
 +
        }
  
    select {atomIndex=cdidx}
+
        # Else fail
    setDistanceIdx(nidx, cdidx, 1.47)
+
        else {
    setAngleIdx(caidx, nidx, cdidx, 102.7)
+
            break
    setDihedralIdx(cbidx, caidx, nidx, cdidx, 16.2)
+
        }
  
    select {atomIndex=cgidx}
+
        # If no movement in 4 tries, we are done
    setDistanceIdx(cdidx, cgidx, 1.51)
+
        if (okCount > 3) {
    setAngleIdx(nidx, cdidx, cgidx, 106.4)
+
            break
     setDihedralIdx(caidx, nidx, cdidx, cgidx, 16.2)
+
        }
 +
     } # endwhile (targetIdx != gCanchorIdx) {
 
}
 
}
  
# Repair side chain
+
# Rebuild Nward rotors set
function repairSideChain(targetIdx, nWard) {
+
function tug_track_n() {
  
     var idx = (nWard ? getCwardBBidx(targetIdx, gChain) : getNwardBBidx(targetIdx, gChain))
+
     gOK = true
  
     if (({atomIndex=targetIdx}.atomName == "CA")
+
     # For all bb atoms nWard of cargo
        and ({atomIndex=targetIdx}.group != "GLY")) {
+
    var targetIdx = gNcargoIdx
        var cbidx = getCBidx(targetIdx)
+
    var okCount = 0
        select none
+
 
         selectAddSideChain(cbidx)
+
    # Allow collisions with cargo
         setAngleIdx(idx, targetIdx, cbidx, 110.0)
+
    gOkCollide = gCargoSet
        setDistanceIdx(targetIdx, cbidx, 1.5)
+
    var tcount = 0
         if ({atomIndex=targetIdx}.group != "PRO") {
+
    while (targetIdx != gNanchorIdx) {
            var colliders = (within(kCtolerance, FALSE, {selected})
+
 
                and not {atomIndex=targetIdx} and not {selected})
+
         # Step to next atom
            if (colliders.size > 0) {
+
         targetIdx = get_nward_bb_idx(targetIdx, gChain)
                if ({atomIndex=targetIdx}.group != "ALA") {
+
         if (targetIdx < 0) {
                    fixSCcollision2(cbidx)
+
             break
                }
 
             }
 
 
         }
 
         }
         else {
+
          
            if (nWard) {
+
        # No collision with cargo allowed after two atoms placed
            }
+
        if (tcount == 2) {
            else {
+
          gOkCollide = ({})
                setDihedralIdx(getNwardBBidx(idx, gChain), idx, targetIdx, cbidx, 174.2)
 
            }
 
 
         }
 
         }
    }
+
        tcount++
 +
 
 +
        # Compute targets desired coords
 +
        var n1idx = get_nward_bb_idx(targetIdx, gChain)
 +
        var c1idx = get_cward_bb_idx(targetIdx, gChain)
 +
        var c2idx = get_cward_bb_idx(c1idx, gChain)
 +
        var c3idx = get_cward_bb_idx(c2idx, gChain)
 +
        var pt = {0 0 0}
 +
        if ({atomIndex=targetIdx}.atomName == "CA") {
  
    else if ({atomIndex=targetIdx}.atomName == "C") {
+
            # Desired target location is trigonal O
        var oidx = getOidx(targetIdx)
+
            var oidx = get_o_idx(c1idx)
        select {atomIndex=oidx}
+
            select {atomIndex=oidx}
        setAngleIdx(idx, targetIdx, oidx, 120.0)
+
            set_distance_idx(c1idx, oidx, 1.39)
        setDistanceIdx(targetIdx, oidx, 1.21)
+
            pt = get_trigonal_idx(c2idx, c1idx, oidx, 1.41)
        if (nWard) {
+
             set_distance_idx(c1idx, oidx, 1.21)
             setDihedralIdx(getCwardBBidx(idx, gChain), idx, targetIdx, oidx, 0.0)
 
 
         }
 
         }
         if ({atomIndex=idx}.group == "PRO") {
+
         else if (({atomIndex=targetIdx}.atomName == "N")
             repairProline(idx)
+
             and ({atomIndex=targetIdx}.group != "GLY")) {
            var dNo = {atomIndex=targetIdx}.atomno + 4
+
 
            var dIdx = {(atomno=dNO) and (chain=gChain)}.atomIndex
+
            # Desired target location is r-tetragonal CB
            var colliders = (within(kCtolerance, FALSE, {atomIndex=dIdx})
+
             var cbidx = get_cb_idx(c1idx)
                and not connected({atomIndex=dIdx})
+
            pt = get_tet_idx(cbidx, c1idx, c2idx, 1.5)
                and not {atomIndex=dIdx})
 
             for (var i = 1; i <= colliders.size; i++) {
 
                if (colliders[i].atomName == "O") {
 
                    counterRotate2(colliders[i].atomIndex,
 
                        {atomIndex=dIdx}.xyz, targetIdx, FALSE)
 
                }
 
            }
 
 
         }
 
         }
    }
+
        else { # C
}
 
  
# Rebuild Cward rotors set
+
            # Save current target coords
function tugTrackC() {
+
            var cp = {atomIndex=targetIdx}.xyz
  
    # For all bb atoms cWard of cargo
+
            # Set target atom at desired distance and angle
    var targetIdx = gCcargoIdx
+
            select {atomIndex=targetIdx}
    var okCount = 0
+
            set_distance_idx(c1idx, targetIdx, 1.37)
 +
            set_angle_idx(c2idx, c1idx, targetIdx, 110.0)
  
    # Allow collisions with cargo
+
            if ({atomIndex=targetIdx}.group == "PRO") {
    gOkCollide = gCargoSet
+
                set_dihedral_idx(c3idx, c2idx, c1idx, targetIdx, -57.0)
    var tcount = 0
+
            }
    while (targetIdx != gCanchorIdx) {
 
  
        # Step to next atom
+
            # Record and restore target
        targetIdx = getCwardBBidx(targetIdx, gChain)
+
            pt = {atomIndex=targetIdx}.xyz
 
+
            {atomIndex=targetIdx}.xyz = cp
        # No collision with cargo allowed after two atoms placed
 
        if (tcount == 2) {
 
          gOkCollide = ({})
 
 
         }
 
         }
        tcount++
 
  
         # Compute targets desired coords
+
         # If target not at desired location
         var c1idx = getCwardBBidx(targetIdx, gChain)
+
         if (distance(pt, {atomIndex=targetIdx}) > kDtolerance) {
        var n1idx = getNwardBBidx(targetIdx, gChain )
+
            var okCount = 0
        var n2idx = getNwardBBidx(n1Idx, gChain)
+
            gTargetPt = pt
        var n3idx = getNwardBBidx(n2Idx, gChain)
+
            var xcount = 0
        var pt = {0 0 0}
+
            gOK = false
        if ({atomIndex=targetIdx}.atomName == "N") {
+
             while ((xcount < 20) and (not gOK)) {
             var oidx = getOidx(n1idx)
 
            select {atomIndex=oidx}
 
  
            # Desired target location is trigonal O
+
                # Rotate on nWard rotor set to move it there
            setDistanceIdx(n1idx, oidx, 1.5)
+
                tug_track_idx(targetIdx, pt, true, false)
            pt = getTrigonal(n2idx, n1idx, oidx, 1.37)
+
                xcount++
             setDistanceIdx(n1idx, oidx, 1.21)
+
             }
 
         }
 
         }
         else if (({atomIndex=targetIdx}.atomName == "C")
+
         else {
             and ({atomIndex=targetIdx}.group != "GLY")) {
+
             gOK = true
 
+
             okCount++
             # Desired target location is tetragonal CB
 
            var cbidx = getCBidx(n1idx)
 
            pt = getTet(n2idx, n1idx, cbidx, 1.5)
 
 
         }
 
         }
        else { # CA (or GLY C)
 
  
            # Save current target coords
+
        # If sucessful
            var cp = {atomIndex=targetIdx}.xyz
+
        if (gOK == true) {
  
             # Set target atom at desired distance and angle
+
             # Adust any side atoms
             select {atomIndex=targetIdx}
+
             repair_sc(targetIdx, true)
            setDistanceIdx(n1idx, targetIdx, 1.5)
+
        }
            setAngleIdx(n2idx, n1idx, targetIdx, 120.0)
 
            if ({atomIndex=targetIdx}.atomName == "CA") {
 
                setDihedralIdx(n3idx, n2idx, n1idx, targetIdx, 180)
 
            }
 
  
            # Record and restore target
+
        # Else fail
            pt = {atomIndex=targetIdx}.xyz
+
        else {
             {atomIndex=targetIdx}.xyz = cp
+
             break
 
         }
 
         }
  
         # If target not at desired location
+
         # If no movement in 4 tries, we are done
         if (distance(pt, {atomIndex=targetIdx}) > kDtolerance) {
+
         if (okCount > 3) {
             okCount = 0
+
             break
            gTargetPt = pt
 
            var xcount = 0
 
            gOK = FALSE
 
            while ((xcount < 20) and (not gOK)) {
 
 
 
                # Rotate on cWard rotor set to move it there
 
                tugTrackIdx(targetIdx, pt, FALSE, FALSE)
 
                xcount++
 
            }
 
        }
 
        else {
 
            gOK = TRUE
 
            okCount++
 
 
         }
 
         }
  
        # If successful
+
    }  # endwhile (targetIdx != gNanchorIdx) {
        if (gOK == TRUE) {
+
}
  
            # Adust any side atoms
+
# gPlicoRecord is maintained by the macro pilcoRecord
            repairSideChain(targetIdx, FALSE)
+
function translate_selected_record(pt) {
        }
+
    if (gPlicoRecord != "") {
 +
        plico_record(format("select %s;translateSelected %s;", {selected}, pt))
 +
    }
 +
    translateSelected @pt
 +
}
  
         # Else fail
+
# gPlicoRecord is maintained by the macro pilcoRecord
         else {
+
function rotate_selected_record(pivotIdx, caxis, a) {
             break
+
    if (gPlicoRecord != "") {
        }
+
         plico_record(format("select %s;", {selected}))
 +
         plico_record(format("rotateSelected {atomIndex=%d} @%s @%s;",
 +
             pivotIdx, caxis, a))
 +
    }
 +
    rotateSelected {atomIndex=pivotIdx} @caxis @a
 +
}
  
         # If no movement in 4 tries, we are done
+
function collect_sc_rotors(no, iChain) {
        if (okCount > 3) {
+
    var scBondIdxs = array()
 +
    for (var iNo = no; iNo >= 0; iNo--) {
 +
        var ile = 0
 +
         switch ({(atomno=iNo) and (chain=iChain)
 +
            and thisModel}.atomName) {
 +
        case "CA" :
 +
            return scBondIdxs # Early exit since break 1 appears broken
 +
        case "CZ" :
 +
            if ({(atomno=iNo) and (chain=iChain)
 +
                and thisModel}.group == "TYR") {
 +
                break
 +
            }
 +
        case "CE" :
 +
            if ({(atomno=iNo) and (chain=iChain)
 +
                and thisModel}.group == "MET") {
 +
                break
 +
            }
 +
        case "CG1" :
 +
            if ({(atomno=iNo) and (chain=iChain)
 +
                and thisModel}.group == "VAL") {
 +
                break
 +
            }
 +
            if ({(atomno=iNo) and (chain=iChain)
 +
                and thisModel}.group == "ILE") {
 +
                ile = 1
 +
            }
 +
        case "NE" :
 +
        case "CD" :
 +
        case "SD" :
 +
        case "CG" :
 +
        case "CB" :
 +
            scBondIdxs += {(atomno=@{iNo+1+ile}) and (chain=iChain)
 +
                and thisModel}.atomIndex
 +
            scBondIdxs += {(atomno=@{iNo+0}) and (chain=iChain)
 +
                and thisModel}.atomIndex
 +
            if ({(atomno=iNo) and (chain=iChain)
 +
                and thisModel}.atomName%2 == "CG") {
 +
                scBondIdxs += {(atomno=@{iNo-1}) and (chain=iChain)
 +
                    and thisModel}.atomIndex
 +
                scBondIdxs += {(atomno=@{iNo-4}) and (chain=iChain)
 +
                    and thisModel}.atomIndex
 +
            }
 +
            else if ({(atomno=iNo) and (chain=iChain)
 +
                and thisModel}.atomName == "CB") {
 +
                scBondIdxs += {(atomno=@{iNo-3}) and (chain=iChain)
 +
                    and thisModel}.atomIndex
 +
                scBondIdxs += {(atomno=@{iNo-4}) and (chain=iChain)
 +
                    and thisModel}.atomIndex
 +
            }
 +
            else {
 +
                scBondIdxs += {(atomno=@{iNo-1}) and (chain=iChain)
 +
                    and thisModel}.atomIndex
 +
                scBondIdxs += {(atomno=@{iNo-2}) and (chain=iChain)
 +
                    and thisModel}.atomIndex
 +
            }
 
             break
 
             break
 
         }
 
         }
     } # endwhile (targetIdx != gCanchorIdx) {
+
 
 +
     }
 +
 
 +
    return scBondIdxs
 
}
 
}
  
# Rebuild Nward rotors set
+
# Drag Side Chain
function tugTrackN() {
+
function drag_sc() {
 +
    var iNo = {atomIndex=gSCidx}.atomno
 +
    var iChain  = {atomIndex=gSCidx}.chain
 +
    if ({atomIndex=gSCidx}.group != "PRO") {
  
    gOK = TRUE
+
        var scBondIdxs = collect_sc_rotors( iNo, iChain)
 +
        var numChi = scBondIdxs.size / 4
 +
        var dist = distance({atomIndex=gSCidx}.xyz, gSCpt)
 +
        var scSet = get_sc_set(gSCidx, iChain)
  
    # For all bb atoms nWard of cargo
+
        # For all rotor combinations
    var targetIdx = gNcargoIdx
+
        var dh = array()
    var okCount = 0
+
        for (var i = 0; i < numChi; i++) {
 
+
            dh += angle({atomIndex=@{scBondIdxs[4+(4*i)]}},
    # Allow collisions with cargo
+
                {atomIndex=@{scBondIdxs[3+(4*i)]}},
    gOkCollide = gCargoSet
+
                {atomIndex=@{scBondIdxs[2+(4*i)]}},
    var tcount = 0
+
                {atomIndex=@{scBondIdxs[1+(4*i)]}})
    while (targetIdx != gNanchorIdx) {
+
        }
 +
        for (var i = 0; i < numChi; i++) {
 +
            var rot = -120
 +
            for (var j = 0; j < 6; j++) {
 +
                rot += 60*j
 +
                select_add_sc(scBondIdxs[1+(4*i)])
 +
                set_dihedral_idx(scBondIdxs[4+(4*i)], scBondIdxs[3+(4*i)],
 +
                    scBondIdxs[2+(4*i)], scBondIdxs[1+(4*i)], rot)
 +
                var newDist = distance({atomIndex=gSCidx}.xyz, gSCpt)
 +
                if (gSCcheck) {
 +
                    var colliders = (within(kCtolerance, false, scSet)
 +
                        and not connected(scSet) and not {scSet})
 +
                    if (colliders) {
 +
                        continue
 +
                    }
 +
                }
  
        # Step to next atom
+
                # Find the best
        targetIdx = getNwardBBidx(targetIdx, gChain)
+
                if (newDist < dist) {
 +
                    dist = newDist
 +
                    for (var k = 0; k < numChi; k++) {
 +
                        dh[k+1] = angle({atomIndex=@{scBondIdxs[4+(4*k)]}},
 +
                        {atomIndex=@{scBondIdxs[3+(4*k)]}},
 +
                        {atomIndex=@{scBondIdxs[2+(4*k)]}},
 +
                        {atomIndex=@{scBondIdxs[1+(4*k)]}})
 +
                    }
 +
                }
 +
            }
 +
        }
  
         # No collision with cargo allowed after two atoms placed
+
         # Now set the best
         if (tcount == 2) {
+
        for (var i = 0; i < numChi; i++) {
          gOkCollide = ({})
+
            select_add_sc(scBondIdxs[1+(4*i)])
 +
            set_dihedral_idx(scBondIdxs[4+(4*i)], scBondIdxs[3+(4*i)],
 +
                scBondIdxs[2+(4*i)], scBondIdxs[1+(4*i)], dh[i+1])
 +
        }
 +
       
 +
         if (gSCcheck = false) {
 +
            plico_minimize( scSet)
 
         }
 
         }
         tcount++
+
    }
 +
    else { # PRO - toggle between puckers up and down
 +
         var icd = {(atomno=@{iNo+1}) and (chain=iChain)
 +
            and thisModel}.atomIndex
 +
        var icb = {(atomno=@{iNo-1}) and (chain=iChain)
 +
            and thisModel}.atomIndex
 +
        var ica = {(atomno=@{iNo-4}) and (chain=iChain)
 +
            and thisModel}.atomIndex
 +
        var in = {(atomno=@{iNo-5}) and (chain=iChain)
 +
            and thisModel}.atomIndex
 +
        select {atomIndex=gSCidx}
  
         # Compute targets desired coords
+
         if (angle({atomIndex=ica}, {atomIndex=in},
        var n1idx = getNwardBBidx(targetIdx, gChain)
+
            {atomIndex=icd}, {atomIndex=gSCidx}) < -10.0) {
        var c1idx = getCwardBBidx(targetIdx, gChain)
+
             set_dihedral_idx(ica, in, icd, gSCidx, 8.7)
        var c2idx = getCwardBBidx(c1idx, gChain)
+
             set_angle_idx(in, icd, gSCidx, 110.0)
        var c3idx = getCwardBBidx(c2idx, gChain)
+
             set_distance_idx(icd, gSCidx, 1.5)
        var pt = {0 0 0}
 
        if ({atomIndex=targetIdx}.atomName == "CA") {
 
 
 
            # Desired target location is trigonal O
 
             var oidx = getOidx(c1idx)
 
            select {atomIndex=oidx}
 
            setDistanceIdx(c1idx, oidx, 1.39)
 
             pt = getTrigonal(c2idx, c1idx, oidx, 1.41)
 
             setDistanceIdx(c1idx, oidx, 1.21)
 
 
         }
 
         }
         else if (({atomIndex=targetIdx}.atomName == "N")
+
         else {
             and ({atomIndex=targetIdx}.group != "GLY")) {
+
             set_dihedral_idx(ica, in, icd, gSCidx, -29.5)
 
+
             set_angle_idx(in, icd, gSCidx, 108.8)
            # Desired target location is r-tetragonal CB
+
             set_distance_idx(icd, gSCidx, 1.5)
             var cbidx = getCBidx(c1idx)
 
             pt = getTet(cbidx, c1idx, c2idx, 1.5)
 
 
         }
 
         }
        else { # C
+
    }
  
            # Save current target coords
+
    draw gSCcircle CIRCLE {atomIndex=gSCidx} MESH NOFILL
            var cp = {atomIndex=targetIdx}.xyz
+
    gSCpt = {atomIndex=gSCidx}.xyz
 +
}
  
            # Set target atom at desired distance and angle
+
# Fix side chain collisions
            select {atomIndex=targetIdx}
+
function fix_sc_collision_2(idx) {
            setDistanceIdx(c1idx, targetIdx, 1.37)
+
    gOk2 = false
            setAngleIdx(c2idx, c1idx, targetIdx, 110.0)
+
    var iNo = {atomIndex=idx}.atomno
 +
    var iChain = {atomIndex=idx}.chain
 +
    var resno = {(atomno=iNo) and (chain=iChain)
 +
        and thisModel}.resno
  
            if ({atomIndex=targetIdx}.group == "PRO") {
+
    # Get SC terminus
                setDihedralIdx(c3idx, c2idx, c1idx, targetIdx, -57.0)
+
    while (resno == {(atomno=iNo) and (chain=iChain)
            }
+
        and thisModel}.resno) {
 +
        iNo++
 +
    }
 +
    iNo--
  
            # Record and restore target
+
    var sc = array()
            pt = {atomIndex=targetIdx}.xyz
+
    var iBno = iNo
             {atomIndex=targetIdx}.xyz = cp
+
    while ({(atomno=iBno) and (chain=iChain)
         }
+
        and thisModel}.atomName != "CB") {
 +
        sc += {(atomno=iBno) and (chain=iChain)
 +
             and thisModel}
 +
        iBno--
 +
    }
 +
    var cbidx = {(atomno=iBno) and (chain=iChain)
 +
         and thisModel}.atomIndex
  
        # If target not at desired location
+
    var scBondIdxs = collect_sc_rotors( iNo, iChain)
        if (distance(pt, {atomIndex=targetIdx}) > kDtolerance) {
+
    var numChi = scBondIdxs.size / 4
            var okCount = 0
 
            gTargetPt = pt
 
            var xcount = 0
 
            gOK = FALSE
 
            while ((xcount < 20) and (not gOK)) {
 
  
                # Rotate on nWard rotor set to move it there
+
    # For all rotor combinations
                tugTrackIdx(targetIdx, pt, TRUE, FALSE)
+
    for (var i = 0; i < numChi; i++) {
                xcount++
+
         var rot = -120
            }
+
         for (var j = 0; j < 6; j++) {
         }
+
             rot += 60
         else {
+
             select_add_sc(scBondIdxs[1+(4*i)])
             gOK = TRUE
+
            set_dihedral_idx(scBondIdxs[1+(4*i)], scBondIdxs[2+(4*i)],
             okCount++
+
                scBondIdxs[3+(4*i)], scBondIdxs[4+(4*i)], rot)
        }
 
  
        # If sucessful
+
            # If no collision, exit
        if (gOK == TRUE) {
+
            var colliders = (within(kCtolerance, false, {sc})
 +
                and not {atomIndex=cbidx} and not {sc})
  
             # Adust any side atoms
+
             # If it is with water, delete the water
             repairSideChain(targetIdx, TRUE)
+
             for (var c = 1; c < colliders.size; c++ ) {
        }
+
                if (colliders[c].group = "HOH") {
 +
                    delete {atomIndex=@{colliders[c].atomIndex}}
 +
                    colliders = {colliders and not @{colliders[c]}}
 +
                }
 +
            }
  
        # Else fail
+
            if (colliders.size == 0) {
        else {
+
                gOk2 = true
            break
+
                return # Early exit since break 1 appears broken
        }
+
            }
  
        # If no movement in 4 tries, we are done
 
        if (okCount > 3) {
 
            break
 
 
         }
 
         }
 
    }  # endwhile (targetIdx != gNanchorIdx) {
 
}
 
 
# gPlicoRecord is maintained by the macro pilcoRecord
 
function plicoRecord(s) {
 
    var g = format("show file \"%s\"", gPlicoRecord)
 
    var ls = script(g)
 
    if (ls.find("FileNotFoundException")) {
 
        ls = ""
 
 
     }
 
     }
    ls += s
 
    write var ls @gPlicoRecord
 
 
}
 
}
  
# gPlicoRecord is maintained by the macro pilcoRecord
+
function is_moveable_sc(aIdx) {
function translateSelectedRecord(pt) {
 
    if (gPlicoRecord != "") {
 
        plicoRecord(format("select %s;translateSelected %s;", {selected}, pt))
 
    }
 
    translateSelected @pt
 
}
 
  
# gPlicoRecord is maintained by the macro pilcoRecord
+
    var ret = (({atomIndex=aIdx}.group != "PRO")
function rotateSelectedRecord(pivotIdx, axis, a) {
+
         or ({atomIndex=aIdx}.atomName == "CG"))
    if (gPlicoRecord != "") {
+
    switch({atomIndex=aIdx}.atomName) {
         plicoRecord(format("select %s;", {selected}))
+
    case "N":
        plicoRecord(format("rotateSelected {atomIndex=%d} @%s @%s;",
+
    case "CA":
            pivotIdx, axis, a))
+
    case "C":
 +
    case "CB":
 +
    case "O":
 +
    case "O4\'":
 +
        ret = false
 +
        break
 
     }
 
     }
     rotateSelected {atomIndex=pivotIdx} @axis @a
+
     return ret
 
}
 
}
  
function collectSCrotors(no, iChain) {
+
function is_rotor_avail(i1idx, i2idx) {
     var scBondIdxs = array()
+
     var ret = true
     for (var iNo = no; iNo >= 0; iNo--) {
+
    if (i1idx > i2idx) { # frozen bonds are kept as lo-hi
         var ile = 0
+
        var idx = @i1idx
         switch ({(atomno=iNo) and (chain=iChain)}.atomName) {
+
        i1idx = @i2idx
        case "CA" :
+
        i2idx = @idx
            return scBondIdxs # Early exit since break 1 appears broken
+
    }
        case "CZ" :
+
 
            if ({(atomno=iNo) and (chain=iChain)}.group == "TYR") {
+
     for (var i = 1; i <= gFreeze.size; i += 2) {
                 break
+
        if ((gFreeze[i] == i1idx) and (gFreeze[i+1] == i2idx)) {
 +
            ret = false
 +
            break
 +
        }
 +
    }
 +
 
 +
    return ret
 +
}
 +
 
 +
function xcollect_bb_rotors(nWard) {
 +
    var anchorNo = (nWard
 +
        ? ((gNanchorIdx >= 0) ? {atomIndex=gNanchorIdx}.atomno : gMinNo)
 +
         : ((gCanchorIdx >= 0) ? {atomIndex=gCanchorIdx}.atomno : gMaxNo))
 +
    var cargoNo = (nWard
 +
        ? ((gNcargoIdx >= 0) ? {atomIndex=gNcargoIdx}.atomno
 +
         : {atomIndex=gCcargoIdx}.atomno)
 +
        : {atomIndex=gCcargoIdx}.atomno)
 +
    var rotors = array()
 +
    if (cargoNo < anchorNo) {
 +
 
 +
        for (var iNo = cargoNo; iNo <= anchorNo; iNo++) {
 +
            if ({(atomno=iNo) and (chain=gChain)
 +
                and thisModel}.atomName == "CA") {
 +
                if (is_rotor_avail(iNo)) {# xxx
 +
                    if (({(atomno=iNo) and (chain=gChain)
 +
                        and thisModel}.group != "PRO") and (iNo > cargoNo)) { # phi
 +
                        rotors += [{(atomno=@{get_cm_no(iNo-1)}) and (chain=gChain)
 +
                            and thisModel}.atomIndex,
 +
                            {(atomno=@{iNo-1}) and (chain=gChain)
 +
                                and thisModel}.atomIndex]
 +
                        rotors += [{(atomno=@{iNo}) and (chain=gChain)
 +
                            and thisModel}.atomIndex,
 +
                            {(atomno=@{iNo+1}) and (chain=gChain)
 +
                                and thisModel}.atomIndex]
 +
                    }
 +
                    if (iNo != (anchorNo-1)) { # psi
 +
                        rotors += [{(atomno=@{iNo-1}) and (chain=gChain)
 +
                            and thisModel}.atomIndex,
 +
                            {(atomno=@{iNo}) and (chain=gChain)
 +
                                and thisModel}.atomIndex]
 +
                        rotors += [{(atomno=@{iNo+1}) and (chain=gChain)
 +
                            and thisModel}.atomIndex,
 +
                            {(atomno=@{get_np_no(iNo+2)}) and (chain=gChain)
 +
                                and thisModel}.atomIndex]
 +
                    }
 +
                 }
 
             }
 
             }
         case "CE" :
+
         }
            if ({(atomno=iNo) and (chain=iChain)}.group == "MET") {
+
    }
                break
+
    else {
            }
+
 
        case "CG1" :
+
        for (var iNo = cargoNo; iNo >= anchorNo; iNo--) {
             if ({(atomno=iNo) and (chain=iChain)}.group == "VAL") {
+
             if ({(atomno=iNo) and (chain=gChain)
                 break
+
                and thisModel}.atomName == "CA") {
            }
+
                 if (is_rotor_avail(iNo)) {
            if ({(atomno=iNo) and (chain=iChain)}.group == "ILE") {
+
                    if ((iNo != (anchorNo-1)) and (iNo < cargoNo)) { # psi
                ile = 1
+
                        rotors += [{(atomno=@{get_np_no(iNo+2)}) and (chain=gChain)
            }
+
                            and thisModel}.atomIndex,
        case "NE" :
+
                            {(atomno=@{iNo+1}) and (chain=gChain)
        case "CD" :
+
                                and thisModel}.atomIndex]
        case "SD" :
+
                        rotors += [{(atomno=@{iNo}) and (chain=gChain)
        case "CG" :
+
                            and thisModel}.atomIndex,
        case "CB" :
+
                            {(atomno=@{iNo-1}) and (chain=gChain)
            scBondIdxs += {(atomno=@{iNo+1+ile}) and (chain=iChain)}.atomIndex
+
                                and thisModel}.atomIndex]
            scBondIdxs += {(atomno=@{iNo+0}) and (chain=iChain)}.atomIndex
+
                    }
            if ({(atomno=iNo) and (chain=iChain)}.atomName%2 == "CG") {
+
                    if ({(atomno=iNo) and (chain=gChain)
                scBondIdxs += {(atomno=@{iNo-1}) and (chain=iChain)}.atomIndex
+
                        and thisModel}.group != "PRO") { # phi
                scBondIdxs += {(atomno=@{iNo-4}) and (chain=iChain)}.atomIndex
+
                        rotors += [{(atomno=@{iNo+1}) and (chain=gChain)
            }
+
                            and thisModel}.atomIndex,
            else if ({(atomno=iNo) and (chain=iChain)}.atomName == "CB") {
+
                            {(atomno=@{iNo}) and (chain=gChain)
                scBondIdxs += {(atomno=@{iNo-3}) and (chain=iChain)}.atomIndex
+
                                and thisModel}.atomIndex]
                scBondIdxs += {(atomno=@{iNo-4}) and (chain=iChain)}.atomIndex
+
                        rotors += [{(atomno=@{iNo-1}) and (chain=gChain)
            }
+
                            and thisModel}.atomIndex,
            else {
+
                            {(atomno=@{get_cm_no(iNo-1)}) and (chain=gChain)
                scBondIdxs += {(atomno=@{iNo-1}) and (chain=iChain)}.atomIndex
+
                                and thisModel}.atomIndex]
                scBondIdxs += {(atomno=@{iNo-2}) and (chain=iChain)}.atomIndex
+
                    }
 +
                }
 
             }
 
             }
            break
 
 
         }
 
         }
 +
    }
  
 +
    if (nWard) {
 +
        gNrotors = rotors
 +
    }
 +
    else {
 +
        gCrotors = rotors
 +
    }
 +
}
 +
function collect_bb_rotors(nWard) {
 +
    if (nWard) {
 +
        gNrotors = array()
 +
        var nres = {atomIndex=gNcargoIdx}.resno
 +
        var nname = {atomIndex=gNcargoIdx}.atomName
 +
        var ares = get_resno_min(gChain)
 +
        var aA = get_atom_rcn( ares, gChain, "N")
 +
        if (gNanchorIdx >= 0) {
 +
            aA = {atomIndex=gNanchorIdx}
 +
        }
 +
        var aname = aA.atomName
 +
        for (var r = nres; r >= ares; r--) {
 +
            var aCp = get_atom_rcn( r-1, gChain, "C")
 +
            var aN = get_atom_rcn( r, gChain, "N")
 +
            var aCa = get_atom_rcn( r, gChain, "CA")
 +
            var aC = get_atom_rcn( r, gChain, "C")
 +
            var aNn = get_atom_rcn( r+1, gChain, "N")
 +
            if (aCp.size < 1) {
 +
                aCp = aNn
 +
            }
 +
            # psi
 +
            if (((r < nres) and (r > ares))
 +
                or ((r == nres) and (nname == "C"))
 +
                or ((r == ares) and (aname != "C"))) {
 +
                if (is_rotor_avail(aCa.atomIndex, aC.atomIndex)) {
 +
                    gNrotors += aNn.atomIndex
 +
                    gNrotors += aC.atomIndex
 +
                    gNrotors += aCa.atomIndex
 +
                    gNrotors += aN.atomIndex
 +
                }
 +
            }
 +
                       
 +
            # phi
 +
            if (aCa.group != "PRO") {
 +
                if (((r <= nres) and (r > ares))
 +
                    or ((r == ares) and (aname == "N"))) {
 +
                    if (is_rotor_avail(aCa.atomIndex, aN.atomIndex)) {
 +
                        gNrotors += aC.atomIndex
 +
                        gNrotors += aCa.atomIndex
 +
                        gNrotors += aN.atomIndex
 +
                        gNrotors += aCp.atomIndex
 +
                    }
 +
                }
 +
            }
 +
        } # endfor
 +
    }
 +
    else {
 +
        gCrotors = array()
 +
        var cres = {atomIndex=gCcargoIdx}.resno
 +
        var cname = {atomIndex=gCcargoIdx}.atomName
 +
        var ares = get_resno_max(gChain)
 +
        var aA = get_atom_rcn( ares, gChain, "C")
 +
        if (gCanchorIdx >= 0) {
 +
            aA = {atomIndex=gCanchorIdx}
 +
        }
 +
        var aname = aA.atomName
 +
        for (var r = cres; r <= ares; r++) {
 +
            var aCp = get_atom_rcn( r-1, gChain, "C")
 +
            var aN = get_atom_rcn( r, gChain, "N")
 +
            var aCa = get_atom_rcn( r, gChain, "CA")
 +
            var aC = get_atom_rcn( r, gChain, "C")
 +
            var aNn = get_atom_rcn( r+1, gChain, "N")
 +
            if (aNn.size < 1) {
 +
                aNn = aCp
 +
            }
 +
           
 +
            # phi
 +
            if (aCa.group != "PRO") {
 +
                if (((r > cres) and (r < ares))
 +
                    or ((r == cres) and (aname == "N"))
 +
                    or ((r == ares) and (aname != "N"))) {
 +
                    if (is_rotor_avail(aN.atomIndex, aCa.atomIndex)) {
 +
                        gCrotors += aCp.atomIndex
 +
                        gCrotors += aN.atomIndex
 +
                        gCrotors += aCa.atomIndex
 +
                        gCrotors += aC.atomIndex
 +
                    }
 +
                }
 +
            }
 +
           
 +
            # psi
 +
            if (((r >= cres) and (r < ares))
 +
                or ((r == cres) and (aname != "C"))
 +
                or ((r == ares) and (aname == "C"))) {
 +
                if (is_rotor_avail(aCa.atomIndex, aC.atomIndex)) {
 +
                    gCrotors += aN.atomIndex
 +
                    gCrotors += aCa.atomIndex
 +
                    gCrotors += aC.atomIndex
 +
                    gCrotors += aNn.atomIndex
 +
                }
 +
            }
 +
        } # endfor
 
     }
 
     }
 
    return scBondIdxs
 
 
}
 
}
  
# Drag Side Chain
+
function collect_rotors() {
function dragSC() {
+
    collect_bb_rotors(false)
 
+
     collect_bb_rotors(true)
     var iNo = {atomIndex=gSCidx}.atomno
+
}
    var iChain  = {atomIndex=gSCidx}.chain
 
  
    if ({atomIndex=gSCidx}.group != "PRO") {
+
function tug_sc(pt) {
  
        var scBondIdxs = collectSCrotors( iNo, iChain)
+
    # If destination atom defined
         var numChi = scBondIdxs.size / 4
+
    if (gDestAtomIdx >= 0) {
         var dist = distance({atomIndex=gSCidx}.xyz, gSCpt)
+
         var v = {atomIndex=gDestAtomIdx}.xyz - {atomIndex=gSCidx}.xyz
 
+
         if (abs(angle({atomIndex=gDestAtomIdx}.xyz, {0 0 0}, pt)) < 90) {
        var scSet = ({})
+
             pt = -v/20.0
        if (gSCcheck) {
 
             scSet = getScSet(gSCidx, iChain)
 
 
         }
 
         }
          
+
         else {
        # For all rotor combinations
+
             pt = v/20.0
        var dh = array()
 
        for (var i = 0; i < numChi; i++) {
 
             dh += angle({atomIndex=@{scBondIdxs[4+(4*i)]}},
 
                {atomIndex=@{scBondIdxs[3+(4*i)]}},
 
                {atomIndex=@{scBondIdxs[2+(4*i)]}},
 
                {atomIndex=@{scBondIdxs[1+(4*i)]}})
 
        }
 
        for (var i = 0; i < numChi; i++) {
 
            var rot = -120
 
            for (var j = 0; j < 6; j++) {
 
                rot += 60*j
 
                selectAddSideChain(scBondIdxs[1+(4*i)])
 
                setDihedralIdx(scBondIdxs[4+(4*i)], scBondIdxs[3+(4*i)],
 
                    scBondIdxs[2+(4*i)], scBondIdxs[1+(4*i)], rot)
 
                var newDist = distance({atomIndex=gSCidx}.xyz, gSCpt)
 
                if (gSCcheck) {
 
                    var colliders = (within(kCtolerance, FALSE, scSet)
 
                        and not connected(scSet) and not {scSet})
 
                    if (colliders.size > 0) {
 
                        continue
 
                    }
 
                }
 
               
 
                # Find the best
 
                if (newDist < dist) {
 
                    dist = newDist
 
                    for (var k = 0; k < numChi; k++) {
 
                        dh[k+1] = angle({atomIndex=@{scBondIdxs[4+(4*k)]}},
 
                        {atomIndex=@{scBondIdxs[3+(4*k)]}},
 
                        {atomIndex=@{scBondIdxs[2+(4*k)]}},
 
                        {atomIndex=@{scBondIdxs[1+(4*k)]}})
 
                    }
 
                }
 
            }
 
 
         }
 
         }
 +
    }
 +
    gSCpt += pt
 +
    draw arrow {atomIndex=gSCidx} @gSCpt
 +
}
 +
 +
function set_colors() {
 +
    select (thisModel)
 +
    color {selected} @gScheme
 +
    color {atomIndex=g1pivotIdx} green
 +
    color {atomIndex=g2pivotIdx} green
 +
    color @gCargoSet @gAltScheme
 +
    color {gCargoSet and oxygen} pink
 +
    select {(atomIndex=gCcargoIdx) or (atomIndex=gNcargoIdx)
 +
        or (atomIndex=gCanchorIdx) or (atomIndex=gNanchorIdx)}
 +
    halo on
 +
    select {atomIndex=gDestAtomIdx}
 +
    star on
 +
    select none
 +
}
 +
 +
function clear_atom_idxs() {
 +
    gCcargoIdx = -1
 +
    gNcargoIdx = -1
 +
    gCanchorIdx = -1
 +
    gNanchorIdx = -1
 +
    g1pivotIdx = -1
 +
    g2pivotIdx = -1
 +
    g1dynamicIdx = -1
 +
    g2dynamicIdx = -1
 +
    gDestAtomIdx = -1
 +
    gSCidx = -1
 +
}
  
         # Now set the best
+
function timed_out (s) {
         for (var i = 0; i < numChi; i++) {
+
    timeout ID"tug" OFF
             selectAddSideChain(scBondIdxs[1+(4*i)])
+
    refresh
            setDihedralIdx(scBondIdxs[4+(4*i)], scBondIdxs[3+(4*i)],
+
    if (prompt(format("%s - Undo?", s), "Yes|No", true) == "Yes") {
                scBondIdxs[2+(4*i)], scBondIdxs[1+(4*i)], dh[i+1])
+
        gBusy = false
 +
        restore state gState
 +
        connect
 +
        select gCargoSet
 +
         set bondPicking true
 +
        refresh
 +
         for (var i = 1; i <= gFreeze.size; i+=2) {
 +
             select {atomIndex=@{gFreeze[i]}} or {atomIndex=@{gFreeze[i+1]}}
 +
            color bonds lightblue
 
         }
 
         }
 +
        background ECHO yellow
 +
        echo @gEcho
 +
        select all
 +
        quit
 
     }
 
     }
    else { # PRO - toggle between puckers up and down
+
}
        var icd = {(atomno=@{iNo+1}) and (chain=iChain)}.atomIndex
 
        var icb = {(atomno=@{iNo-1}) and (chain=iChain)}.atomIndex
 
        var ica = {(atomno=@{iNo-4}) and (chain=iChain)}.atomIndex
 
        var in = {(atomno=@{iNo-5}) and (chain=iChain)}.atomIndex
 
        select {atomIndex=gSCidx}
 
  
        if (angle({atomIndex=ica}, {atomIndex=in},
+
function record_drag() {
            {atomIndex=icd}, {atomIndex=gSCidx}) < -10.0) {
+
    var ls = format("select %s;", {selected})
            setDihedralIdx(ica, in, icd, gSCidx, 8.7)
+
    ls += format("gCanchorIdx = %d;", gCanchorIdx)
            setAngleIdx(in, icd, gSCidx, 110.0)
+
    ls += format("gCanchorNo = %d;", gCanchorNo)
            setDistanceIdx(icd, gSCidx, 1.5)
+
    ls += format("gNanchorIdx = %d;", gNanchorIdx)
        }
+
    ls += format("gNanchorNo = %d;", gNanchorNo)
        else {
+
    ls += format("gCcargoIdx = %d;", gCcargoIdx)
            setDihedralIdx(ica, in, icd, gSCidx, -29.5)
+
    ls += format("gNcargoIdx = %d;", gNcargoIdx)
            setAngleIdx(in, icd, gSCidx, 108.8)
+
    ls += format("gCcargoNo = %d;", gCcargoNo)
            setDistanceIdx(icd, gSCidx, 1.5)
+
    ls += format("gNcargoNo = %d;", gNcargoNo)
        }
+
    ls += format("gDestAtomIdx = %d;", gDestAtomIdx)
     }
+
    ls += format("g1pivotIdx = %d;", g1pivotIdx)
 
+
    ls += format("g2pivotIdx = %d;", g2pivotIdx)
     draw gSCcircle CIRCLE {atomIndex=gSCidx} MESH NOFILL
+
    ls += format("gOkCollide = %s;", gOkCollide)
     gSCpt = {atomIndex=gSCidx}.xyz
+
     ls += format("gChain = \"%s\";", gChain)
}
+
     ls += format("gMinNo = %d;", gMinNo)
 
+
     ls += format("gMaxNo = %d;", gMaxNo)
# Fix side chain collisions
+
    ls += format("gCargoSet = %s;", gCargoSet)
function fixSCcollision2(idx) {
+
     ls += format("gSCidx = %d;", gSCidx)
     gOk2 = FALSE
+
     ls += format("gSCcircle = %d;", gSCcircle)
     var iNo = {atomIndex=idx}.atomno
+
     ls += format("gSCpt = %s;", gSCpt)
     var iChain = {atomIndex=idx}.chain
+
     ls += "collect_rotors();"
     var resno = {(atomno=iNo) and (chain=iChain)}.resno
+
    ls += "tug_drag_done_mb();"
 +
    plico_record(ls)
 +
}
  
     # Get SC terminus
+
# Pick call-back for freeze
    while (resno == {(atomno=iNo) and (chain=iChain)}.resno) {
+
function tug_pick_cb() {
        iNo++
+
     if (_pickInfo[3][6] == "bond") {
    }
+
        var sel = {selected}
    iNo--
+
        var i = _pickInfo.find(":")
 +
        var iChain = _pickInfo[i+1]
 +
        i = _pickInfo.find("#")
 +
        var a1no = 0 + _pickInfo[i+1][i+3]
 +
        var j = _pickInfo[i+1][9999].find("#")
 +
        var a2no = 0 + _pickInfo[i+j+1][i+j+3]
 +
        var i1idx = {(atomno=a1no) and (chain=iChain)
 +
            and thisModel}.atomIndex
 +
        var i2idx = {(atomno=a2no) and (chain=iChain)
 +
            and thisModel}.atomIndex
 +
        if (({atomIndex=i1idx}.atomName == "CA")
 +
            or ({atomIndex=i2idx}.atomName == "CA")) {
 +
            if (({atomIndex=i1idx}.atomName != "CB")
 +
                and ({atomIndex=i2idx}.atomName != "CB")) {
 +
                if (i1idx > i2idx) {
 +
                    var idx = 0 + i1idx
 +
                    i1idx = 0 + i2idx
 +
                    i2idx = 0 + idx
 +
                }
 +
                select {atomIndex=i1idx} or {atomIndex=i2idx}
  
    var sc = array()
+
                for (i = 1; i <= gFreeze.size; i += 2) {
    var iBno = iNo
+
                    if ((gFreeze[i] == i1idx) and (gFreeze[i+1] == i2idx)) {
    while ({(atomno=iBno) and (chain=iChain)}.atomName != "CB") {
+
                        if (i > 1) {
        sc += {(atomno=iBno) and (chain=iChain)}
+
                            if (gFreeze.size = (i+1)) {
        iBno--
+
                                gFreeze = gFreeze[1][i-1]
    }
+
                            }
    var cbidx = {(atomno=iBno) and (chain=iChain)}.atomIndex
+
                            else {
 
+
                                gFreeze = gFreeze[1][i-1] + gFreeze[i+2][0]
    var scBondIdxs = collectSCrotors( iNo, iChain)
+
                            }
    var numChi = scBondIdxs.size / 4
+
                        }
 
+
                        else {
    # For all rotor combinations
+
                            gFreeze = gFreeze[i+2][0]
    for (var i = 0; i < numChi; i++) {
+
                        }
        var rot = -120
+
                        color bonds NONE
        for (var j = 0; j < 6; j++) {
+
                        i = 0
            rot += 60
+
                        break
            selectAddSideChain(scBondIdxs[1+(4*i)])
+
                    }
            setDihedralIdx(scBondIdxs[1+(4*i)], scBondIdxs[2+(4*i)],
+
                 }
                scBondIdxs[3+(4*i)], scBondIdxs[4+(4*i)], rot)
+
                if (i > gFreeze.size) {
               
+
                    gFreeze += i1idx
            # If no collision, exit
+
                     gFreeze += i2idx
            colliders = (within(kCtolerance, FALSE, {sc})
+
                     color bonds lightblue
                 and not {atomIndex=cbidx} and not {sc})
 
 
 
            # If it is with water, delete the water
 
            for (var c = 1; c < colliders.size; c++ ) {
 
                if (colliders[c].group = "HOH") {
 
                     delete {atomIndex=@{colliders[c].atomIndex}}
 
                     colliders = {colliders and not @{colliders[c]}}
 
 
                 }
 
                 }
 
             }
 
             }
 
            if (colliders.size == 0) {
 
                gOk2 = TRUE
 
                return # Early exit since break 1 appears broken
 
            }
 
 
 
         }
 
         }
 +
        select {sel}
 
     }
 
     }
 
}
 
}
  
function isMovableSideChain(aIdx) {
+
# Bound to LEFT-UP by tug_enable_drag
 +
function tug_drag_done_mb() {
 +
    if (not gBusy) {
 +
        if (gPlicoRecord != "") {
 +
            record_drag()
 +
        }
  
    var ret = (({atomIndex=aIdx}.group != "PRO")
+
        # Move by rotation on rotor sets, smallest first
         or ({atomIndex=aIdx}.atomName == "CG"))
+
         gBusy = true
    switch({atomIndex=aIdx}.atomName) {
+
         background ECHO pink
    case "N":
+
         refresh
    case "CA":
 
    case "C":
 
    case "CB":
 
    case "O":
 
    case "O4\'":
 
         ret = FALSE
 
         break
 
    }
 
    return ret
 
}
 
  
# gFreeze is maintained by the script plicoFreeze that allows the user
+
        # If side chain mode
# to inhibit rotation on selected rotors
+
        if (gSCidx >= 0) {
function isRotorAvailable(idx) {
+
            drag_sc()
    return (gFreeze.find(idx) == 0)
+
        }
}
 
  
function collectBBrotors(nWard) {
+
         # Else
    var anchorNo = (nWard
+
         else if (not gTow) {
         ? ((gNanchorIdx >= 0) ? {atomIndex=gNanchorIdx}.atomno : gMinNo)
+
            gOK = true
         : ((gCanchorIdx >= 0) ? {atomIndex=gCanchorIdx}.atomno : gMaxNo))
+
            timeout ID"tug" 20.0 "timed_out(\"Tug timed out\")"
    var cargoNo = (nWard
+
            if ((gCrotors.size < gNrotors.size) or (gNanchorIdx < 0)) {
        ? ((gNcargoIdx >= 0) ? {atomIndex=gNcargoIdx}.atomno
+
                 if (gCrotors.size > 4) {
        : {atomIndex=gCcargoIdx}.atomno)
+
                    tug_track_c()
        : {atomIndex=gCcargoIdx}.atomno)
+
                }
    var rotors = array()
+
                if (gOK and (gNrotors.size > 4)) {
    if (cargoNo < anchorNo) {
+
                    tug_track_n()
 
 
        for (var iNo = cargoNo; iNo <= anchorNo; iNo++) {
 
            if ({(atomno=iNo) and (chain=gChain)}.atomName == "CA") {
 
                 if (isRotorAvailable(iNo)) {
 
                    if (({(atomno=iNo) and (chain=gChain)}.group != "PRO") and (iNo > cargoNo)) { # phi
 
                        rotors += [{(atomno=@{getCmNo(iNo-1)}) and (chain=gChain)}.atomIndex,
 
                            {(atomno=@{iNo-1}) and (chain=gChain)}.atomIndex]
 
                        rotors += [{(atomno=@{iNo}) and (chain=gChain)}.atomIndex,
 
                            {(atomno=@{iNo+1}) and (chain=gChain)}.atomIndex]
 
                    }
 
                    if (iNo != (anchorNo-1)) { # psi
 
                        rotors += [{(atomno=@{iNo-1}) and (chain=gChain)}.atomIndex,
 
                            {(atomno=@{iNo}) and (chain=gChain)}.atomIndex]
 
                        rotors += [{(atomno=@{iNo+1}) and (chain=gChain)}.atomIndex,
 
                            {(atomno=@{getNpNo(iNo+2)}) and (chain=gChain)}.atomIndex]
 
                    }
 
 
                 }
 
                 }
 
             }
 
             }
        }
+
            else {
    }
+
                if (gNrotors.size > 4) {
    else {
+
                     tug_track_n()
 
+
                }
        for (var iNo = cargoNo; iNo >= anchorNo; iNo--) {
+
                if (gOK and (gCrotors.size > 4)) {
            if ({(atomno=iNo) and (chain=gChain)}.atomName == "CA") {
+
                     tug_track_c()
                if (isRotorAvailable(iNo)) {
 
                     if ((iNo != (anchorNo-1)) and (iNo < cargoNo)) { # psi
 
                        rotors += [{(atomno=@{getNpNo(iNo+2)}) and (chain=gChain)}.atomIndex,
 
                            {(atomno=@{iNo+1}) and (chain=gChain)}.atomIndex]
 
                        rotors += [{(atomno=@{iNo}) and (chain=gChain)}.atomIndex,
 
                            {(atomno=@{iNo-1}) and (chain=gChain)}.atomIndex]
 
                    }
 
                     if ({(atomno=iNo) and (chain=gChain)}.group != "PRO") { # phi
 
                        rotors += [{(atomno=@{iNo+1}) and (chain=gChain)}.atomIndex,
 
                            {(atomno=@{iNo}) and (chain=gChain)}.atomIndex]
 
                        rotors += [{(atomno=@{iNo-1}) and (chain=gChain)}.atomIndex,
 
                            {(atomno=@{getCmNo(iNo-1)}) and (chain=gChain)}.atomIndex]
 
                    }
 
 
                 }
 
                 }
 
             }
 
             }
        }
+
            timeout ID"tug" OFF
    }
 
  
    if (nWard) {
+
            # If anchor angles acute, fail
        gNrotors = rotors
+
            if (gOK == true) {
    }
+
                if (gCanchorIdx >= 0) {
    else {
+
                    var ic = get_cward_bb_idx(gCanchorIdx, gChain)
        gCrotors = rotors
+
                    var in = get_nward_bb_idx(gCanchorIdx, gChain)
    }
+
                    if ((ic >= 0) and
}
+
                        angle({atomIndex=ic}, {atomIndex=gCanchorIdx}, {atomIndex=in})
 +
                        < 100.0) {
 +
                        gOK = false
 +
                    }
 +
                }
 +
                if (gNanchorIdx >= 0) {
 +
                    var ic = get_cward_bb_idx(gNanchorIdx, gChain)
 +
                    var in = get_nward_bb_idx(gNanchorIdx, gChain)
 +
                    if ((in >= 0) and
 +
                        angle({atomIndex=ic}, {atomIndex=gNanchorIdx}, {atomIndex=in})
 +
                        < 100.0) {
 +
                        gOK = false
 +
                    }
 +
                }
 +
            }
  
function collectRotors() {
+
            # If too far
    collectBBrotors(FALSE)
+
            if (not gOK) {
    collectBBrotors(TRUE)
+
                timed_out("TUG TOO FAR!")
}
+
            }
  
function tugSideChain(pt) {
+
            # Else OK
 +
            else {
  
    # If destination atom defined
+
                var idx = {
    if (gDestAtomIdx >= 0) {
+
                    (atomno=@{{(chain=gChain) and thisModel}.atomno.min})
        var v = {atomIndex=gDestAtomIdx}.xyz - {atomIndex=gSCidx}.xyz
+
                    and (chain=gChain) and thisModel}.atomIndex
        if (abs(angle({atomIndex=gDestAtomIdx}.xyz, {0 0 0}, pt)) < 90) {
+
 
            pt = -v/20.0
+
 
        }
+
                var ihc = 0
        else {
+
                for (ihc = 0; ihc < 10; ihc++) {
             pt = v/20.0
+
                    select ((atomno >= gNanchorNo) and (atomno <= gCanchorNo)
 +
                        and (chain = gChain) and thisModel)
 +
                    handle_collisions( idx)
 +
                    if (count_collisions(({})).size == 0) {
 +
                        break
 +
                    }
 +
                }
 +
                if (ihc == 10) {
 +
                    timed_out("Unable to handle all collisions!")
 +
                }
 +
             }
 
         }
 
         }
 +
        select {gCargoSet}
 +
        gBusy = false
 +
        background ECHO yellow
 +
        set bondPicking true
 +
        refresh
 
     }
 
     }
    gSCpt += pt
+
}
    draw arrow {atomIndex=gSCidx} @gSCpt
 
}
 
  
function setColors() {
+
# Bound to ALT-SHIFT-LEFT-DRAG by tug_enable_drag
     select all
+
function tug_drag_2_mb() {
    color {selected} @gScheme
+
     tug_drag_mb(true)
    color {atomIndex=g1pivotIdx} green
 
    color {atomIndex=g2pivotIdx} green
 
    color @gCargoSet @gAltScheme
 
    select {(atomIndex=gCcargoIdx) or (atomIndex=gNcargoIdx)
 
        or (atomIndex=gCanchorIdx) or (atomIndex=gNanchorIdx)}
 
    halo on
 
    select {atomIndex=gDestAtomIdx}
 
    star on
 
    select none
 
 
}
 
}
  
function clearAtomIdxs() {
+
# Bound to ALT-LEFT-DRAG by tug_enable_drag
     gCcargoIdx = -1
+
function tug_drag_mb(alt) {
    gNcargoIdx = -1
+
     if (not gBusy) {
    gCanchorIdx = -1
+
        gBusy = true
    gNanchorIdx = -1
+
        var dx = (40.0 * (_mouseX - gMouseX))/_width
    g1pivotIdx = -1
+
        var dy = (40.0 * (_mouseY - gMouseY))/_height
    g2pivotIdx = -1
+
        var q = quaternion()
    g1dynamicIdx = -1
+
        var ptd = {@dx @dy 0}
    g2dynamicIdx = -1
+
        var pt = (!q)%ptd
    gDestAtomIdx = -1
+
        var caxis = {0 0 0}
    gSCidx = -1
+
        if (distance(pt,  {0 0 0}) > 0.004) {
}
+
            # If sidechain mode
 +
            if (gSCidx >= 0) {
 +
                if ({atomIndex=gSCidx}.atomName == "O") {
 +
                    if ({atomIndex=gSCidx}.group != "PRO") {
 +
                        var dir = ((abs(dx) > abs(dy))
 +
                            ? ((dx < 0) ? 10 : -10)
 +
                            : ((dy < 0) ? 1 : -1))
 +
                        counter_rotate(gSCidx, dir, not alt)
 +
                    }
 +
                }
 +
                else {
 +
                    gSCcheck = not alt
 +
                    tug_sc(pt)
 +
                }
 +
            }
  
function timedOut (s) {
+
            # Else
    timeout ID"tug" OFF
+
            else {
    p = prompt(format("%s - Undo?", s), "Yes|No", TRUE)
 
    if (p == "Yes") {
 
        gBusy = FALSE
 
        background ECHO yellow
 
        restore state gState
 
        select gCargoSet
 
        refresh
 
        quit
 
    }
 
}
 
  
function recordDrag() {
+
                # If new drag
    var ls = format("select %s;", {selected})
+
                if (gNewDrag) {
    ls += format("gCanchorIdx = %d;", gCanchorIdx)
+
                    gNewDrag = false
    ls += format("gCanchorNo = %d;", gCanchorNo)
+
                    save state gState
    ls += format("gNanchorIdx = %d;", gNanchorIdx)
+
                }
    ls += format("gNanchorNo = %d;", gNanchorNo)
 
    ls += format("gCcargoIdx = %d;", gCcargoIdx)
 
    ls += format("gNcargoIdx = %d;", gNcargoIdx)
 
    ls += format("gCcargoNo = %d;", gCcargoNo)
 
    ls += format("gNcargoNo = %d;", gNcargoNo)
 
    ls += format("gDestAtomIdx = %d;", gDestAtomIdx)
 
    ls += format("g1pivotIdx = %d;", g1pivotIdx)
 
    ls += format("g2pivotIdx = %d;", g2pivotIdx)
 
    ls += format("gOkCollide = %s;", gOkCollide)
 
    ls += format("gChain = \"%s\";", gChain)
 
    ls += format("gMinNo = %d;", gMinNo)
 
    ls += format("gMaxNo = %d;", gMaxNo)
 
    ls += format("gCargoSet = %s;", gCargoSet)
 
    ls += format("gSCidx = %d;", gSCidx)
 
    ls += format("gSCcircle = %d;", gSCcircle)
 
    ls += format("gSCpt = %s;", gSCpt)
 
    ls += "collectRotors();"
 
    ls += "tugDragDoneMB();"
 
    plicoRecord(ls)
 
}
 
  
# Bound to LEFT-UP by tugEnableDrag
+
                # If destination atom defined
function tugDragDoneMB() {
+
                if (gDestAtomIdx >= 0) {
    if (not gBusy) {
+
                    var v = {atomIndex=gDestAtomIdx}.xyz - {selected}.xyz
        if (gPlicoRecord != "") {
+
                    if (abs(angle({atomIndex=gDestAtomIdx}.xyz, {0 0 0}, pt)) < 90) {
            recordDrag()
+
                        pt = -v/20.0
        }
+
                    }
 +
                    else {
 +
                        pt = v/20.0
 +
                    }
 +
                }
  
        # Move by rotation on rotor sets, smallest first
+
                # Move the cargo
        gBusy = TRUE
+
                select {gCargoSet}
        background ECHO pink
 
        refresh
 
  
        # If side chain mode
+
                # If pivots defined, rotate it
        if (gSCidx >= 0) {
+
                if (g1pivotIdx >= 0) {
            dragSC()
+
 
        }
+
                    # If two pivots
 +
                    if (g2pivotIdx >= 0) {
 +
                        caxis = {atomIndex=g2pivotIdx}
 +
                    }
 +
 
 +
                    # Else
 +
                    else {
 +
                        caxis = cross(pt, {0 0 0}) + {atomIndex=g1pivotIdx}.xyz
 +
                    }
 +
 
 +
                    var dir = ((abs(dx) > abs(dy))
 +
                        ? ((dx < 0) ? 2 : -2)
 +
                        : ((dy < 0) ? 2 : -2))
 +
                    rotate_selected_record(g1pivotIdx, caxis, dir)
  
        # Else
 
        else if (not gTow) {
 
            gOK = TRUE
 
            timeout ID"tug" 20.0 "timedOut(\"Tug timed out\")"
 
            if ((gCrotors.size < gNrotors.size) or (gNanchorIdx < 0)) {
 
                if (gCrotors.size > 4) {
 
                    tugTrackC()
 
 
                 }
 
                 }
                 if (gOK and (gNrotors.size > 4)) {
+
 
                    tugTrackN()
+
                 # Else translate it
                 }
+
                 else {
            }
+
                     translate_selected_record(pt)
            else {
 
                if (gNrotors.size > 4) {
 
                     tugTrackN()
 
 
                 }
 
                 }
                if (gOK and (gCrotors.size > 4)) {
 
                    tugTrackC()
 
                }
 
            }
 
            timeout ID"tug" OFF
 
  
            # If anchor angles acute, fail
+
                # If collisions
            if (gOK == TRUE) {
+
                 var cNotSels = (within(kCtolerance, false, {selected})
                 if (gCanchorIdx >= 0) {
+
                     and not {gMovingSet})
                    var ic = getCwardBBidx(gCanchorIdx, gChain)
+
                if ((cNotSels) and (not alt)) {
                     var in = getNwardBBidx(gCanchorIdx, gChain)
+
                     gOk2 = true
                    if ((ic >= 0) and
+
                     for (var i = 1; i <= cNotSels.size;  i++) {
                        angle({atomIndex=ic}, {atomIndex=gCanchorIdx}, {atomIndex=in})
 
                        < 100.0) {
 
                        gOK = FALSE
 
                     }
 
                }
 
                if (gNanchorIdx >= 0) {
 
                     var ic = getCwardBBidx(gNanchorIdx, gChain)
 
                    var in = getNwardBBidx(gNanchorIdx, gChain)
 
                    if ((in >= 0) and
 
                        angle({atomIndex=ic}, {atomIndex=gNanchorIdx}, {atomIndex=in})
 
                        < 100.0) {
 
                        gOK = FALSE
 
                    }
 
                }
 
            }
 
  
            # If too far
+
                        # If net collision vector same as move vector
            if (not gOK) {
+
                        var cSels = (within(kCtolerance, false, cNotSels[i]) and {selected})
                timedOut("TUG TOO FAR!")
+
                        for (var j = 1; j <= cSels.size;  j++) {
            }
+
                            var v1 = cNotSels[i].xyz - cSels[j].xyz
 +
                            if (abs(angle(v1, {0 0 0}, pt)) < 90) {
  
            # Else OK
+
                                # If tow mode
            else {
+
                                if (gTow) {
 
 
                select ((atomno >= gNanchorNo) and (atomno <= gCanchorNo)
 
                    and (chain = gChain))
 
                var idx = {(atomno=@{{chain=gChain}.atomno.min})
 
                    and (chain=gChain)}.atomIndex
 
  
 +
                                    # Make a dynamic pivot
 +
                                    if (g1pivotIdx < 0) {
 +
                                        g1pivotIdx = cSels[j].atomIndex
 +
                                        g1dynamicIdx = cNotSels[i].atomIndex
 +
                                        color {atomIndex=g1pivotIdx} lightgreen
 +
                                        set_distance_idx(cNotSels[i].atomIndex,
 +
                                            cSels[j].atomIndex,
 +
                                            kCtolerance + kDtolerance)
 +
                                    }
 +
                                    else if (g2pivotIdx < 0) {
 +
                                        g2pivotIdx = cSels[j].atomIndex
 +
                                        g2dynamicIdx = cNotSels[i].atomIndex
 +
                                        color {atomIndex=g2pivotIdx} lightgreen
 +
                                        set_distance_idx(cNotSels[i].atomIndex,
 +
                                            cSels[j].atomIndex,
 +
                                            kCtolerance + kDtolerance)
 +
                                    }
 +
                                    else {
 +
                                        gOk2 = false
 +
                                    }
 +
                                }
 +
                                else {
  
                var ihc = 0
+
                                    # Try to resolve
                for (ihc = 0; ihc < 10; ihc++) {
+
                                    select @{cSels[j]}
                    handleCollisions2( idx)
+
                                    var idx = {(atomno=@{{(chain=gChain)
                    if (countCollisions(({})) == 0) {
+
                                        and thisModel}.atomno.min})
                         break
+
                                        and (chain=gChain) and thisModel}.atomIndex
                    }
+
                                    handle_collisions( idx)
                }
+
                                }
                if (ihc == 10) {
+
                            }
                    timedOut("Unable to handle all collisions!")
+
                        } # endfor
 +
                        if (not gOk2) {
 +
                            break
 +
                        }
 +
                    } # endfor
 +
 
 +
                    # If unable
 +
                    if (not gOk2) {
 +
 
 +
                         # Back off
 +
                        background ECHO pink
 +
                        delay 1
 +
                        if (g1pivotIdx >= 0) {
 +
                            rotate_selected_record(g1pivotIdx, caxis, -a)
 +
                        }
 +
                        else {
 +
                            translate_selected_record(-pt)
 +
                        }
 +
                        background ECHO yellow
 +
                        set bondPicking true
 +
                    }
 
                 }
 
                 }
 
             }
 
             }
        }
 
        select {gCargoSet}
 
        gBusy = FALSE
 
        background ECHO yellow
 
        refresh
 
    }
 
}
 
  
# Bound to ALT-SHIFT-LEFT-DRAG by tugEnableDrag
+
            # If dynamic pivots
function tugDrag2MB() {
+
            if (g1dynamicIdx >= 0) {
    tugDragMB(TRUE)
+
                var v1 = {atomIndex=g1dynamicIdx}.xyz - {atomIndex=g1pivotIdx}.xyz
}
+
                if (abs(angle(v1, {0 0 0}, pt)) > 90) {
 +
                    color {atomIndex=g1pivotIdx} @gAltScheme
 +
                    g1pivotIdx = -1
 +
                    g1dynamicIdx = -1
  
# Bound to ALT-LEFT-DRAG by tugEnableDrag
 
function tugDragMB(alt) {
 
    if (not gBusy) {
 
        gBusy = TRUE
 
        var dx = (40.0 * (_mouseX - gMouseX))/_width
 
        var dy = (40.0 * (_mouseY - gMouseY))/_height
 
        var q = quaternion()
 
        var ptd = {@dx @dy 0}
 
        var pt = (!q)%ptd
 
        var axis = {0 0 0}
 
        if (distance(pt,  {0 0 0}) > 0.004) {
 
            # If sidechain mode
 
            if (gSCidx >= 0) {
 
                if ({atomIndex=gSCidx}.atomName == "O") {
 
                    dir = ((abs(dx) > abs(dy))
 
                        ? ((dx < 0) ? 2 : -2)
 
                        : ((dy < 0) ? 2 : -2))
 
                    counterRotate(gSCidx, dir, not alt)
 
                }
 
                else {
 
                    gSCcheck = not alt
 
                    tugSideChain(pt)
 
 
                 }
 
                 }
 
             }
 
             }
 
+
             if (g2dynamicIdx >= 0) {
             # Else
+
                 var v1 = {atomIndex=g2dynamicIdx}.xyz - {atomIndex=g2pivotIdx}.xyz
            else {
+
                 if (abs(angle(v1, {0 0 0}, pt)) > 90) {
 
+
                     color {atomIndex=g2pivotIdx} @gAltScheme
                 # If new drag
+
                     g2pivotIdx = -1
                 if (gNewDrag) {
+
                    g2dynamicIdx = -1
                     gNewDrag = FALSE
 
                     save state gState
 
 
                 }
 
                 }
 +
            }
  
                # If destination atom defined
+
            gMouseX = _mouseX
                if (gDestAtomIdx >= 0) {
+
            gMouseY = _mouseY
                    var v = {atomIndex=gDestAtomIdx}.xyz - {selected}.xyz
+
        }
                    if (abs(angle({atomIndex=gDestAtomIdx}.xyz, {0 0 0}, pt)) < 90) {
+
        select {gCargoSet}
                        pt = -v/20.0
+
        gBusy = false
                    }
+
    }
                    else {
+
}
                        pt = v/20.0
 
                    }
 
                }
 
  
                # Move the cargo
+
# Bound to ALT-LEFT-DOWN by tug_enable_drag
                select {gCargoSet}
+
function tug_mark_mb() {
 +
    gMouseX = _mouseX
 +
    gMouseY = _mouseY
 +
    gNewDrag = true
 +
}
  
                # If pivots defined, rotate it
+
# Called by tug_cargo_mb
                if (g1pivotIdx >= 0) {
+
function tug_enable_drag() {
 +
    gEcho = "__________TUG__________|ALT-CLICK=mark block|SHIFT-CLICK=anchors" +
 +
        "|ALT-CTRL-CLICK=pivots|ALT-SHIFT-CLICK=dest atom|ALT-DRAG=move" +
 +
        "|ALT-SHIFT-DRAG=alt move|CLICK bond=freeze|ALT-DOUBLE-CLICK=undo" +
 +
        "|SHIFT-DOUBLE-CLICK=exit"
 +
    echo @gEcho
  
                    # If two pivots
+
    # Allow atoms to be dragged
                    if (g2pivotIdx >= 0) {
+
    bind "ALT-LEFT-DOWN" "tug_mark_mb";
                        axis = {atomIndex=g2pivotIdx}
+
    bind "ALT-LEFT-UP" "tug_drag_done_mb";
                    }
+
    bind "ALT-SHIFT-LEFT-DOWN" "tug_mark_mb";
 +
    bind "ALT-LEFT-DRAG" "tug_drag_mb";
 +
    bind "ALT-SHIFT-LEFT-DRAG" "tug_drag_2_mb";
  
                    # Else
+
    unbind "SHIFT-LEFT-CLICK"
                    else {
+
    bind "SHIFT-LEFT-CLICK" "_pickAtom";
                        axis = cross(pt, {0 0 0}) + {atomIndex=g1pivotIdx}.xyz
+
    bind "SHIFT-LEFT-CLICK" "+:tug_anchor_mb";
                    }
 
  
                    dir = ((abs(dx) > abs(dy))
+
    bind "ALT-CTRL-LEFT-CLICK" "_pickAtom";
                        ? ((dx < 0) ? 2 : -2)
+
    bind "ALT-CTRL-LEFT-CLICK" "+:tug_pivot_mb";
                        : ((dy < 0) ? 2 : -2))
 
                    rotateSelectedRecord(g1pivotIdx, axis, dir)
 
  
                }
+
    bind "ALT-SHIFT-LEFT-CLICK" "_pickAtom";
 +
    bind "ALT-SHIFT-LEFT-CLICK" "+:tug_dest_atom_mb";
 +
}
  
                # Else translate it
+
# Bound to SHIFT-LEFT-CLICK by tug_cargo_mb
                else {
+
function tug_anchor_mb() {
                    translateSelectedRecord(pt)
+
    if ({atomIndex=_atomPicked}.chain == gChain) {
                }
+
        var aPidx = get_sc_bb_idx( _atomPicked, gChain)
  
                # If collisions
+
        var pno = {atomIndex=aPidx}.atomno
                var cNotSels = (within(kCtolerance, FALSE, {selected})
+
        if (pno > {atomIndex=gCcargoIdx}.atomno) {
                    and not {gMovingSet})
+
            select {atomIndex=gCanchorIdx}
                if ((cNotSels.size > 0) and (not alt)) {
+
            halo off
                    gOk2 = TRUE
+
            if (gCanchorIdx == aPidx) {
                    for (var i = 1; i <= cNotSels.size;  i++) {
+
                gCanchorIdx = -1
 
+
                gCanchorNo = gMaxNo + 1
                        # If net collision vector same as move vector
+
            }
                        cSels = (within(kCtolerance, FALSE, cNotSels[i]) and {selected})
+
            else {
                        for (var j = 1; j <= cSels.size;  j++) {
+
                gCanchorIdx = aPidx
                            var v1 = cNotSels[i].xyz - cSels[j].xyz
+
                gCanchorNo = {atomIndex=gCanchorIdx}.atomno
                            if (abs(angle(v1, {0 0 0}, pt)) < 90) {
+
                select {atomIndex=gCanchorIdx}
 
+
                halo on
                                # If tow mode
+
            }
                                if (gTow) {
+
            collect_bb_rotors(false)
 
+
        }
                                    # Make a dynamic pivot
+
        else if (pno < {atomIndex=gNcargoIdx}.atomno) {
                                    if (g1pivotIdx < 0) {
+
            select {atomIndex=gNanchorIdx}
                                        g1pivotIdx = cSels[j].atomIndex
+
            halo off
                                        g1dynamicIdx = cNotSels[i].atomIndex
+
            if (gNanchorIdx == aPidx) {
                                        color {atomIndex=g1pivotIdx} lightgreen
+
                gNanchorIdx = -1
                                        setDistanceIdx(cNotSels[i].atomIndex,
+
                gNanchorNo = gMinNo - 1
                                            cSels[j].atomIndex,
+
            }
                                            kCtolerance + kDtolerance)
+
            else {
                                    }
+
                gNanchorIdx = aPidx
                                    else if (g2pivotIdx < 0) {
+
                gNanchorNo = {atomIndex=gNanchorIdx}.atomno
                                        g2pivotIdx = cSels[j].atomIndex
+
                select {atomIndex=gNanchorIdx}
                                        g2dynamicIdx = cNotSels[i].atomIndex
+
                halo on
                                        color {atomIndex=g2pivotIdx} lightgreen
+
            }
                                        setDistanceIdx(cNotSels[i].atomIndex,
+
            collect_bb_rotors(true)
                                            cSels[j].atomIndex,
+
        }
                                            kCtolerance + kDtolerance)
+
        else {
                                    }
+
            tow_cargo_mb()
                                    else {
+
        }
                                        gOk2 = FALSE
 
                                    }
 
                                }
 
                                else {
 
  
                                    # Try to resolve
+
        # Get moving atoms set
                                    select cSels[j]
+
        gMovingSet = {((atomno < gCanchorNo) and (atomno > gNanchorNo)
                                    var idx = {(atomno=@{{chain=gChain}.atomno.min})
+
            and (chain=gChain) and thisModel)}
                                        and (chain=gChain)}.atomIndex
+
    }
                                    handleCollisions2( idx)
+
    select {gCargoSet}
                                }
+
}
                            }
 
                        } # endfor
 
                        if (not gOk2) {
 
                            break
 
                        }
 
                    } # endfor
 
  
                    # If unable
+
# Bound to ALT-SHIFT-LEFT-CLICK by tug_cargo_mb
                    if (not gOk2) {
+
function tug_dest_atom_mb() {
 +
    var aOk = true
 +
    if ({atomIndex=_atomPicked}.chain == gChain) {
  
                        # Back off
+
        var pno = {atomIndex=_atomPicked}.atomno
                        background ECHO pink
+
        if ((pno <= {atomIndex=gCcargoIdx}.atomno)
                        delay 1
+
             and (pno >= {atomIndex=gNcargoIdx}.atomno)) {
                        if (g1pivotIdx >= 0) {
+
            aOk = false
                            rotateSelectedRecord(g1pivotIdx, axis, -a)
+
        }
                        }
+
    }
                        else {
+
    if (aOk) {
                            translateSelectedRecord(-pt)
+
        select {atomIndex=gDestAtomIdx}
                        }
+
        star off
                        background ECHO yellow
+
        if (gDestAtomIdx == _atomPicked) {
                    }
+
            gDestAtomIdx = -1
                }
+
        }
             }
+
        else {
 
+
             gDestAtomIdx = _atomPicked
            # If dynamic pivots
+
             select {atomIndex=gDestAtomIdx}
            if (g1dynamicIdx >= 0) {
+
             star on
                var v1 = {atomIndex=g1dynamicIdx}.xyz - {atomIndex=g1pivotIdx}.xyz
 
                if (abs(angle(v1, {0 0 0}, pt)) > 90) {
 
                    color {atomIndex=g1pivotIdx} @gAltScheme
 
                    g1pivotIdx = -1
 
                    g1dynamicIdx = -1
 
 
 
                }
 
            }
 
            if (g2dynamicIdx >= 0) {
 
                var v1 = {atomIndex=g2dynamicIdx}.xyz - {atomIndex=g2pivotIdx}.xyz
 
                if (abs(angle(v1, {0 0 0}, pt)) > 90) {
 
                    color {atomIndex=g2pivotIdx} @gAltScheme
 
                    g2pivotIdx = -1
 
                    g2dynamicIdx = -1
 
                }
 
             }
 
 
 
             gMouseX = _mouseX
 
             gMouseY = _mouseY
 
 
         }
 
         }
 
         select {gCargoSet}
 
         select {gCargoSet}
        gBusy = FALSE
 
 
     }
 
     }
 
}
 
}
  
# Bound to ALT-LEFT-DOWN by tugEnableDrag
+
# Bound to CTRL-LEFT-CLICK by tug_cargo_mb
function tugMarkMB() {
+
function tug_pivot_mb() {
     gMouseX = _mouseX
+
     if (g1pivotIdx == _atomPicked) {
     gMouseY = _mouseY
+
        color {atomIndex=g1pivotIdx} @gScheme
     gNewDrag = TRUE
+
        if (g2pivotIdx >= 0) {
}
+
            g1pivotIdx = g2pivotIdx
 +
            g2pivotIdx = -1
 +
            g2dynamicIdx = -1
 +
        }
 +
        else {
 +
            g1pivotIdx = -1
 +
            g1dynamicIdx = -1
 +
        }
 +
    }
 +
     else if (g2pivotIdx == _atomPicked) {
 +
        color {atomIndex=g2pivotIdx} @gScheme
 +
        g2pivotIdx = -1
 +
        g2dynamicIdx = -1
 +
    }
 +
     else if (g1pivotIdx >= 0) {
 +
        if (g2pivotIdx >= 0) {
 +
            color {atomIndex=g2pivotIdx} @gScheme
 +
        }
  
# Called by tugCargoMB
+
        g2pivotIdx = _atomPicked
function tugEnableDrag() {
+
        g2dynamicIdx = -1
     gEcho = "__________TUG__________|ALT-CLICK=mark block|SHIFT-CLICK=anchors" +
+
        color {atomIndex=g2pivotIdx} green
        "|ALT-CTRL-CLICK=pivots|ALT-SHIFT-CLICK=dest atom|ALT-DRAG=move" +
+
    }
        "|SHIFT-ALT-DRAG=alt move|DOUBLE-CLICK=exit"
+
    else {
     echo @gEcho
+
        g1pivotIdx = _atomPicked
 +
        g1dynamicIdx = -1
 +
        color {atomIndex=g1pivotIdx} green
 +
    }
 +
    select {gCargoSet}
 +
}
 +
 
 +
# Bound to SHIFT-LEFT-CLICK by plico_tug
 +
function tow_cargo_mb() {
 +
     gTow = true
 +
    gChain = {atomIndex=_atomPicked}.chain
 +
    gMinNo = {(chain=gChain) and thisModel}.atomno.min
 +
    gMaxNo = {(chain=gChain) and thisModel}.atomno.max
 +
    gCcargoIdx = -1
 +
    gNcargoIdx = -1
 +
    gCanchorIdx = -1
 +
    gCanchorNo = gMaxNo + 1
 +
    gNanchorIdx = -1
 +
     gNanchorNo = gMinNo - 1
  
     # Allow atoms to be dragged
+
     # Highlight cargo cluster
     bind "ALT-LEFT-DOWN" "tugMarkMB";
+
     select {(chain=gChain) and thisModel}
     bind "ALT-LEFT-UP" "tugDragDoneMB";
+
     gCargoSet = {selected}
     bind "ALT-SHIFT-LEFT-DOWN" "tugMarkMB";
+
     gMovingSet = {selected}
     bind "ALT-SHIFT-LEFT-UP" "tugDragDoneMB";
+
     set_colors()
    bind "ALT-LEFT-DRAG" "tugDragMB";
 
    bind "ALT-SHIFT-LEFT-DRAG" "tugDrag2MB";
 
  
 +
    # Enable dragging
 +
    tug_enable_drag()
 +
    select {gCargoSet}
 +
    halo off
 +
    var es = gEcho.replace("anchors","mark chain")
 +
    echo @es
 
     unbind "SHIFT-LEFT-CLICK"
 
     unbind "SHIFT-LEFT-CLICK"
 
     bind "SHIFT-LEFT-CLICK" "_pickAtom";
 
     bind "SHIFT-LEFT-CLICK" "_pickAtom";
     bind "SHIFT-LEFT-CLICK" "+:tugAnchorMB";
+
     bind "SHIFT-LEFT-CLICK" "+:tow_cargo_mb";
 +
}
  
    bind "ALT-CTRL-LEFT-CLICK" "_pickAtom";
+
# Bound to ALT-LEFT-CLICK by plico_tug or called by plicotoab.toabCargoMB
    bind "ALT-CTRL-LEFT-CLICK" "+:tugPivotMB";
+
function tug_cargo_mb() {
  
     bind "ALT-SHIFT-LEFT-CLICK" "_pickAtom";
+
     # If O or movable side chain atom picked
    bind "ALT-SHIFT-LEFT-CLICK" "+:tugDestAtomMB";
+
     if ((({atomIndex=_atomPicked}.atomName = "O")
}
+
         and ({atomIndex=_atomPicked}.group != "PRO"))
 
+
         or (is_moveable_sc( _atomPicked))) {
# Bound to SHIFT-LEFT-CLICK by tugCargoMB
+
        if (gSCidx >= 0) {
function tugAnchorMB() {
+
             draw gSCcircle DELETE
     if ({atomIndex=_atomPicked}.chain == gChain) {
 
         var aPidx = getScBBidx( _atomPicked, gChain)
 
 
 
        var pno = {atomIndex=aPidx}.atomno
 
         if (pno > {atomIndex=gCcargoIdx}.atomno) {
 
            select {atomIndex=gCanchorIdx}
 
            halo off
 
            if (gCanchorIdx == aPidx) {
 
                gCanchorIdx = -1
 
                gCanchorNo = gMaxNo + 1
 
            }
 
            else {
 
                gCanchorIdx = aPidx
 
                gCanchorNo = {atomIndex=gCanchorIdx}.atomno
 
                select {atomIndex=gCanchorIdx}
 
                halo on
 
             }
 
            collectBBrotors(FALSE)
 
 
         }
 
         }
         else if (pno < {atomIndex=gNcargoIdx}.atomno) {
+
         if (gSCidx == _atomPicked) {
             select {atomIndex=gNanchorIdx}
+
             gSCidx = -1
            halo off
 
            if (gNanchorIdx == aPidx) {
 
                gNanchorIdx = -1
 
                gNanchorNo = gMinNo - 1
 
            }
 
            else {
 
                gNanchorIdx = aPidx
 
                gNanchorNo = {atomIndex=gNanchorIdx}.atomno
 
                select {atomIndex=gNanchorIdx}
 
                halo on
 
            }
 
            collectBBrotors(TRUE)
 
 
         }
 
         }
 
         else {
 
         else {
             towCargoMB()
+
             gSCidx = _atomPicked
 +
            gSCpt = {atomIndex=gSCidx}.xyz
 +
            draw gSCcircle CIRCLE {atomIndex=gSCidx} MESH NOFILL
 
         }
 
         }
 
        # Get moving atoms set
 
        gMovingSet = {((atomno < gCanchorNo) and (atomno > gNanchorNo)
 
            and (chain=gChain))}
 
 
     }
 
     }
     select {gCargoSet}
+
     else {
}
+
        if ({atomIndex=_atomPicked}.chain != gChain) {
 
+
            if (gTow) {
# Bound to ALT-SHIFT-LEFT-CLICK by tugCargoMB
+
                select {(chain=gChain) and thisModel}
function tugDestAtomMB() {
+
                color {selected} @gScheme
    var aOk = TRUE
+
            }
    if ({atomIndex=_atomPicked}.chain == gChain) {
+
            gChain = {atomIndex=_atomPicked}.chain
 
+
            select ({atomIndex=gCcargoIdx} or {atomIndex=gNcargoIdx}
        var pno = {atomIndex=_atomPicked}.atomno
+
                or {atomIndex=gCanchorIdx} or {atomIndex=gNanchorIdx})
        if ((pno <= {atomIndex=gCcargoIdx}.atomno) and (pno >= {atomIndex=gNcargoIdx}.atomno)) {
+
             halo off
             aOk = FALSE
+
            gCcargoIdx = -1
 +
            gNcargoIdx = -1
 +
            gCanchorIdx = -1
 +
            gNanchorIdx = -1
 
         }
 
         }
    }
+
        gTow = false
    if (aOk) {
+
        gMinNo = {(chain=gChain) and thisModel}.atomno.min
         select {atomIndex=gDestAtomIdx}
+
         gMaxNo = {(chain=gChain) and thisModel}.atomno.max
        star off
+
         if (gNanchorIdx < 0) {
         if (gDestAtomIdx == _atomPicked) {
+
             gNanchorNo = gMinNo - 1
             gDestAtomIdx = -1
 
 
         }
 
         }
         else {
+
         if (gCanchorIdx < 0) {
             gDestAtomIdx = _atomPicked
+
             gCanchorNo = gMaxNo + 1
            select {atomIndex=gDestAtomIdx}
 
            star on
 
 
         }
 
         }
         select {gCargoSet}
+
         var aPidx = get_sc_bb_idx( _atomPicked, gChain)
    }
+
 
}
+
        gSCidx = -1
 +
        draw gSCcircle DELETE
 +
 
 +
        # If existing cWard cargo picked
 +
        if (gCcargoIdx == aPidx) {
 +
 
 +
            # Clear the highlight
 +
            select {atomIndex=gCcargoIdx}
 +
            halo off
  
# Bound to CTRL-LEFT-CLICK by tugCargoMB
+
            # If nWard cargo exists, mark it as the cWard cargo
function tugPivotMB() {
+
            if (gNcargoIdx != gCcargoIdx) {
    if (g1pivotIdx == _atomPicked) {
+
                gCcargoIdx = get_cp_idx(gNcargoIdx)
        color {atomIndex=g1pivotIdx} @gScheme
+
                gCcargoNo = {atomIndex=gCcargoIdx}.atomno
        if (g2pivotIdx >= 0) {
+
            }
             g1pivotIdx = g2pivotIdx
+
             else {
            g2pivotIdx = -1
+
                gCcargoIdx = -1
            g2dynamicIdx = -1
+
                gNcargoIdx = -1
 +
            }
 
         }
 
         }
         else {
+
         else if (gNcargoIdx == aPidx) {
            g1pivotIdx = -1
+
            select {atomIndex=gNcargoIdx}
            g1dynamicIdx = -1
+
            halo off
        }
+
            gNcargoIdx = get_nm_idx(gCcargoIdx)
    }
+
             gNcargoNo = {atomIndex=gNcargoIdx}.atomno
    else if (g2pivotIdx == _atomPicked) {
 
        color {atomIndex=g2pivotIdx} @gScheme
 
        g2pivotIdx = -1
 
        g2dynamicIdx = -1
 
    }
 
    else if (g1pivotIdx >= 0) {
 
        if (g2pivotIdx >= 0) {
 
             color {atomIndex=g2pivotIdx} @gScheme
 
 
         }
 
         }
 +
        else if (gCcargoIdx >= 0) {
 +
 +
            var no = {atomIndex=aPidx}.atomno
  
        g2pivotIdx = _atomPicked
+
            # If pick is nWard of it
        g2dynamicIdx = -1
+
            if (no < {atomIndex=gCcargoIdx}.atomno) {
        color {atomIndex=g2pivotIdx} green
 
    }
 
    else {
 
        g1pivotIdx = _atomPicked
 
        g1dynamicIdx = -1
 
        color {atomIndex=g1pivotIdx} green
 
    }
 
    select {gCargoSet}
 
}
 
  
# Bound to SHIFT-LEFT-CLICK by plicoTug
+
                # If exists, clear its highlight
function towCargoMB() {
+
                if (gNcargoIdx != gCcargoIdx) {
    gTow = TRUE
+
                    select {atomIndex=gNcargoIdx}
    gChain = {atomIndex=_atomPicked}.chain
+
                    halo off
    gMinNo = {chain=gChain}.atomno.min
+
                }
    gMaxNo = {chain=gChain}.atomno.max
 
    gCcargoIdx = -1
 
    gNcargoIdx = -1
 
    gCanchorIdx = -1
 
    gCanchorNo = gMaxNo + 1
 
    gNanchorIdx = -1
 
    gNanchorNo = gMinNo - 1
 
  
    # Highlight cargo cluster
+
                # Set new nWard cargo and highlight it
    select {chain=gChain}
+
                gNcargoIdx = get_nm_idx(aPidx)
    gCargoSet = {selected}
+
                gNcargoNo = {atomIndex=gNcargoIdx}.atomno
    gMovingSet = {selected}
+
            }
    setColors()
 
  
    # Enable dragging
+
            # Else cWard
    tugEnableDrag()
+
            else {
    select {gCargoSet}
 
    halo off
 
    var es = gEcho.replace("anchors","mark chain")
 
    echo @es
 
    unbind "SHIFT-LEFT-CLICK"
 
    bind "SHIFT-LEFT-CLICK" "_pickAtom";
 
    bind "SHIFT-LEFT-CLICK" "+:towCargoMB";
 
}
 
  
# Bound to ALT-LEFT-CLICK by plicoTug or called by plicotoab.toabCargoMB
+
                # Clear its old highlight
function tugCargoMB() {
+
                select {atomIndex=gCcargoIdx}
 +
                if (gNcargoIdx != gCcargoIdx) {
 +
                    halo off
 +
                }
  
    # If O or movable side chain atom picked
+
                # Set new cWard cargo and highlight
    if (({atomIndex=_atomPicked}.atomName = "O")
+
                gCcargoIdx = get_cp_idx(aPidx)
        or (isMovableSideChain( _atomPicked))) {
+
                gCcargoNo = {atomIndex=gCcargoIdx}.atomno
        if (gSCidx >= 0) {
+
             }
             draw gSCcircle DELETE
 
        }
 
        if (gSCidx == _atomPicked) {
 
            gSCidx = -1
 
 
         }
 
         }
 +
 +
        # Else no cWard cargo
 
         else {
 
         else {
             gSCidx = _atomPicked
+
 
             gSCpt = {atomIndex=gSCidx}.xyz
+
            # Set new cWard cargo and highlight
             draw gSCcircle CIRCLE {atomIndex=gSCidx} MESH NOFILL
+
             gCcargoIdx = get_cp_idx(aPidx)
 +
             gCcargoNo = {atomIndex=gCcargoIdx}.atomno
 +
            gNcargoIdx = get_nm_idx(gCcargoIdx)
 +
             gNcargoNo = {atomIndex=gNcargoIdx}.atomno
 
         }
 
         }
    }
+
 
    else {
+
        # If any anchor now inside cargo cluster, kill it
         if ({atomIndex=_atomPicked}.chain != gChain) {
+
         if ({atomIndex=gCanchorIdx}.atomno <= {atomIndex=gCcargoIdx}.atomno) {
            if (gTow) {
 
                select {chain=gChain}
 
                color {selected} @gScheme
 
            }
 
            gChain = {atomIndex=_atomPicked}.chain
 
            select ({atomIndex=gCcargoIdx} or {atomIndex=gNcargoIdx}
 
                or {atomIndex=gCanchorIdx} or {atomIndex=gNanchorIdx})
 
            halo off
 
            gCcargoIdx = -1
 
            gNcargoIdx = -1
 
 
             gCanchorIdx = -1
 
             gCanchorIdx = -1
 +
            gCanchorNo = gMaxNo + 1
 +
        }
 +
        if ({atomIndex=gNanchorIdx}.atomno >= {atomIndex=gNcargoIdx}.atomno) {
 
             gNanchorIdx = -1
 
             gNanchorIdx = -1
        }
 
        gTow = FALSE
 
        gMinNo = {chain=gChain}.atomno.min
 
        gMaxNo = {chain=gChain}.atomno.max
 
        if (gNanchorIdx < 0) {
 
 
             gNanchorNo = gMinNo - 1
 
             gNanchorNo = gMinNo - 1
 
         }
 
         }
        if (gCanchorIdx < 0) {
 
            gCanchorNo = gMaxNo + 1
 
        }
 
        var aPidx = getScBBidx( _atomPicked, gChain)
 
  
         gSCidx = -1
+
         # Highlight cargo cluster
         draw gSCcircle DELETE
+
        select_nward_idx(gCcargoIdx, gNcargoIdx)
 +
        gCargoSet = {selected}
 +
         set_colors()
  
         # If existing cWard cargo picked
+
         # Collect the rotor sets
         if (gCcargoIdx == aPidx) {
+
         collect_rotors()
  
            # Clear the highlight
+
        # Get moving atoms set
             select {atomIndex=gCcargoIdx}
+
        gMovingSet = {((atomno < gCanchorNo) and (atomno > gNanchorNo)
            halo off
+
             and (chain=gChain) and thisModel)}
 +
    }
  
            # If nWard cargo exists, mark it as the cWard cargo
+
    # Enable dragging
            if (gNcargoIdx != gCcargoIdx) {
+
    if (gToab) {
                gCcargoIdx = getCpIdx(gNcargoIdx)
+
        to_ab_enable_drag()
                gCcargoNo = {atomIndex=gCcargoIdx}.atomno
+
    }
            }
+
    else {
            else {
+
         tug_enable_drag()
                gCcargoIdx = -1
+
    }
                gNcargoIdx = -1
 
            }
 
        }
 
         else if (gNcargoIdx == aPidx) {
 
            select {atomIndex=gNcargoIdx}
 
            halo off
 
            gNcargoIdx = getNmIdx(gCcargoIdx)
 
            gNcargoNo = {atomIndex=gNcargoIdx}.atomno
 
        }
 
        else if (gCcargoIdx >= 0) {
 
  
            var no = {atomIndex=aPidx}.atomno
+
    select {gCargoSet}
 +
}
  
            # If pick is nWard of it
+
function tug_undo_mb() {
            if (no < {atomIndex=gCcargoIdx}.atomno) {
+
    if (prompt("Undo", "Yes|No", true) == "Yes") {
 +
        restore state gState
 +
    }
 +
}
  
                # If exists, clear its highlight
+
# Top level of Tug
                if (gNcargoIdx != gCcargoIdx) {
+
function plico_tug() {
                    select {atomIndex=gNcargoIdx}
 
                    halo off
 
                }
 
  
                # Set new nWard cargo and highlight it
+
    # Load common functions if not already
                gNcargoIdx = getNmIdx(aPidx)
+
    if (kCommon < 7) {
                gNcargoNo = {atomIndex=gNcargoIdx}.atomno
+
        script $SCRIPT_PATH$plicoCommon.spt
             }
+
        if (kCommon < 7) {
 +
            prompt ("A newer version of plicoCommon.SPT is required")
 +
             quit
 +
        }
 +
    }
  
            # Else cWard
+
    gPlico = "TUG"
            else {
+
    plico_prelim(true, true)
 +
   
 +
    gBondPicking = bondPicking
 +
    set bondPicking true
 +
    set PickCallback "jmolscript:tug_pick_cb"
  
                # Clear its old highlight
+
    gEcho = ("_________TUG_________|ALT-CLICK=mark block|SHIFT-CLICK=mark chain" +
                select {atomIndex=gCcargoIdx}
+
        "|CLICK bond=freeze|ALT-DOUBLE-CLICK=undo|SHIFT-DOUBLE-CLICK=exit")
                if (gNcargoIdx != gCcargoIdx) {
+
    echo @gEcho
                    halo off
+
    gCrotors = array()
                }
+
    gNrotors = array()
 +
    clear_atom_idxs()
 +
    gToab = false
  
                # Set new cWard cargo and highlight
+
    bind "ALT-LEFT-CLICK" "_pickAtom";
                gCcargoIdx = getCpIdx(aPidx)
+
    bind "ALT-LEFT-CLICK" "+:tug_cargo_mb";
                gCcargoNo = {atomIndex=gCcargoIdx}.atomno
+
    bind "SHIFT-LEFT-CLICK" "_pickAtom";
            }
+
    bind "SHIFT-LEFT-CLICK" "+:tow_cargo_mb";
        }
+
    bind "ALT-LEFT-DOUBLE" "tug_undo_mb";
 +
    bind "SHIFT-DOUBLE" "tug_exit(true)";
 +
    bind "LEFT-CLICK" "+:plico_menu_toggle";
 +
}
  
         # Else no cWard cargo
+
# Bound to DOUBLE by plico_tug
         else {
+
function tug_exit() {
 
+
    if (plico_exit()) {
            # Set new cWard cargo and highlight
+
        set bondPicking gBondPicking
            gCcargoIdx = getCpIdx(aPidx)
+
        set PickCallback NONE
            gCcargoNo = {atomIndex=gCcargoIdx}.atomno
+
         select all
            gNcargoIdx = getNmIdx(gCcargoIdx)
+
        color bonds none
            gNcargoNo = {atomIndex=gNcargoIdx}.atomno
+
       
 
+
        reset kTug
            # Set default cWard anchor at cWard N
+
        reset gCanchorIdx 
            var iNo = gMaxNo
+
         reset gCanchorNo 
            for (; iNo > 0; iNo--) {
+
        reset gPlico     
                if ({(atomno=iNo) and (chain=gChain)}.atomName == "N") {
+
        reset gNanchorIdx 
                    break;
+
        reset gNanchorNo 
                }
+
        reset gCcargoIdx  
            }
+
        reset gNcargoIdx  
            gCanchorIdx = {(atomno=iNo) and (chain=gChain)}.atomIndex
+
        reset gCcargoNo   
            gCanchorNo = {atomIndex=gCanchorIdx}.atomno
+
        reset gNcargoNo  
         }
+
        reset gDestAtomIdx
 
+
        reset g1pivotIdx 
         # If any anchor now inside cargo cluster, kill it
+
        reset g2pivotIdx 
         if ({atomIndex=gCanchorIdx}.atomno <= {atomIndex=gCcargoIdx}.atomno) {
+
        reset gSelSaves   
            gCanchorIdx = -1
+
        reset gCrotors   
            gCanchorNo = gMaxNo + 1
+
        reset gNrotors   
         }
+
        reset gOkCollide 
         if ({atomIndex=gNanchorIdx}.atomno >= {atomIndex=gNcargoIdx}.atomno) {
+
        reset gChain     
            gNanchorIdx = -1
+
        reset gMinNo     
            gNanchorNo = gMinNo - 1
+
        reset gMaxNo      
         }
+
        reset gCargoSet   
 
+
        reset gMovingSet 
         # Highlight cargo cluster
+
        reset gSCidx     
         selectNwardIdx(gCcargoIdx, gNcargoIdx)
+
        reset gSCcircle   
         gCargoSet = {selected}
+
        reset gSCpt       
         setColors()
+
        reset gTargetPt   
 +
        reset gNewDrag   
 +
        reset gTow       
 +
        reset g1dynamicIdx
 +
        reset g2dynamicIdx
 +
        reset gSCcheck   
 +
        reset gBondPicking
 +
        reset gFreeze     
 +
        reset gToab       
 +
        reset function "get_cp_idx"
 +
        reset function "get_cm_no"
 +
        reset function "get_nm_idx"
 +
        reset function "get_np_no"
 +
        reset function "get_cb_idx"
 +
        reset function "get_o_idx"
 +
        reset function "get_nward_bb_idx"
 +
        reset function "get_cward_bb_idx"
 +
        reset function "get_sc_set"
 +
        reset function "get_sc_bb_idx"
 +
        reset function "is_bb_idx"
 +
        reset function "is_sc_idx"
 +
        reset function "select_add_sc"
 +
        reset function "handle_collisions"
 +
        reset function "tug_track_idx"
 +
        reset function "do_counter_rotate"
 +
        reset function "counter_rotate"
 +
        reset function "counter_rotate_2"
 +
        reset function "repair_proline"
 +
        reset function "repair_sc"
 +
        reset function "tug_track_c"
 +
        reset function "tug_track_n"
 +
        reset function "translate_selected_record"
 +
        reset function "rotate_selected_record"
 +
        reset function "collect_sc_rotors"
 +
        reset function "drag_sc"
 +
        reset function "fix_sc_collision_2"
 +
        reset function "is_moveable_sc"
 +
        reset function "is_rotor_avail"
 +
        reset function "xcollect_bb_rotors"
 +
        reset function "collect_bb_rotors"
 +
        reset function "collect_rotors"
 +
        reset function "tug_sc"
 +
        reset function "set_colors"
 +
         reset function "clear_atom_idxs"
 +
        reset function "timed_out"
 +
         reset function "record_drag"
 +
         reset function "tug_pick_cb"
 +
        reset function "tug_drag_done_mb"
 +
        reset function "tug_drag_2_mb"
 +
         reset function "tug_drag_mb"
 +
         reset function "tug_mark_mb"
 +
        reset function "tug_anchor_mb"
 +
        reset function "tug_enable_drag"
 +
         reset function "tug_dest_atom_mb"
 +
        reset function "tug_pivot_mb"
 +
         reset function "tow_cargo_mb"
 +
         reset function "tug_cargo_mb"
 +
         reset function "tug_undo_mb"
 +
         reset function "plico_tug"
  
        # Collect the rotor sets
 
        collectRotors()
 
  
        # Get moving atoms set
 
        gMovingSet = {((atomno < gCanchorNo) and (atomno > gNanchorNo)
 
            and (chain=gChain))}
 
 
     }
 
     }
 
    # Enable dragging
 
    if (gToab) {
 
        toabEnableDrag()
 
    }
 
    else {
 
        tugEnableDrag()
 
    }
 
   
 
    select {gCargoSet}
 
}
 
 
function plicoPrelim() {
 
 
    # Push selected
 
    gSelSaves = {selected}
 
    select all
 
   
 
    # Bad idea to proceed when collisions present
 
    while (TRUE) {
 
        var cc = countCollisions(({}))
 
        if (cc > 0) {
 
            var p = prompt(format("%d collision%s present!\nProceed anyway?",
 
                cc, ((cc > 1) ? "s" : "")), "OK|Cancel|Repair", TRUE)
 
            if (p == "Cancel") {
 
                quit
 
            }
 
            else if (p == "Repair") {
 
                select all
 
                allSet = {selected}
 
                gChain = "XX"
 
                for (var idx = {allSet}.atomIndex.min;
 
                    idx <= {allSet}.atomIndex.max; idx++) {
 
                    if ({atomIndex=idx}.chain != gChain) {
 
                        gChain = {atomIndex=idx}.chain
 
                        select {chain=gChain}
 
                        handleCollisions2( idx)
 
                    }
 
                }
 
            }
 
            else {
 
                break
 
            }
 
        }
 
        else {
 
            break
 
        }
 
    } # endwhile
 
 
    gZoom = script("show zoom")
 
    gRotate = script("show rotation")
 
    write tugsave.pdb
 
    select none
 
   
 
    gScheme = defaultColorScheme
 
    gAltScheme = ((gScheme == "Jmol") ? "Rasmol" : "Jmol")
 
    set echo TOP LEFT
 
    background ECHO yellow
 
    gChain = ""
 
    unbind
 
}
 
 
# Top level of Tug
 
function plicoTug() {
 
    gPlico = "TUG"
 
    plicoPrelim()
 
   
 
    gEcho = ("_________TUG_________|ALT-CLICK=mark block|SHIFT-CLICK=mark chain" +
 
        "|DOUBLE-CLICK=exit")
 
    echo @gEcho
 
    gCrotors = array()
 
    gNrotors = array()
 
    clearAtomIdxs()
 
 
    bind "ALT-LEFT-CLICK" "_pickAtom";
 
    bind "ALT-LEFT-CLICK" "+:tugCargoMB";
 
    bind "SHIFT-LEFT-CLICK" "_pickAtom";
 
    bind "SHIFT-LEFT-CLICK" "+:towCargoMB";
 
    bind "DOUBLE" "plicoExit";
 
 
}
 
}
  
# Bound to DOUBLE by plicoTug
+
# End of TUG.SPT
function plicoExit() {
+
</pre>
    var p = ""
 
    if (gPlico.size > 0) {
 
        p = prompt(format("Exit %s?", gPlico), "Yes|No|Undo", TRUE)
 
        if (p == "Undo") {
 
            load tugsave.pdb
 
            script inline gZoom
 
            rotate @gRotate
 
            echo Tug session undone
 
            if (gPlicoRecord != "") {
 
                plicoRecord("load tugsave.pdb;")
 
            }
 
        }
 
    }
 
    if (p != "No") {
 
        unbind
 
        halo off
 
        set allowMoveAtoms FALSE
 
        echo
 
        select all
 
        halo off
 
        star off
 
        color {selected} @gScheme
 
        draw gSCcircle DELETE
 
        gBusy = FALSE
 
        background ECHO yellow
 
 
 
        # Pop selected
 
        select gSelSaves
 
    }
 
}
 
# End of TUG.SPT</pre>
 

Latest revision as of 17:15, 12 April 2016

Tug allows the user to pull or push by mouse actions to move or rotate one part of a polypeptide against the rest by rotation on its psi and phi bonds with collision detection and restriction. It also allows the user to move an entire chain to nest against another chain. A full description of its capabilities can be found here.

Tug is a member of the Plico suite of protein folding tools described in User:Remig/plico . It may be installed and accessed as a macro with the file:

Title=PLICO Tug
Script=script <path to your scripts directory>/tug.spt;plico_tug

saved as plicotug.macro in your .jmol/macros directory as described in Macro.

Copy and paste the following into a text editor and save in your scripts directory as tug.spt.

#   tug - Jmol script by Ron Mignery
#   v1.20 beta    4/12/2016 -axis is now a reserved word
#
#   Translate or rotate a stretch of a polypeptide against itself
#    or against other chains by mouse actions
#
kTug = 3
gCanchorIdx = -1
gCanchorNo = -1
gPlico = "TUG"
gNanchorIdx = -1
gNanchorNo = -1
gCcargoIdx = -1
gNcargoIdx = -1
gCcargoNo = -1
gNcargoNo = -1
gDestAtomIdx = -1
g1pivotIdx = -1
g2pivotIdx = -1
gSelSaves = ({})
gCrotors = array()
gNrotors = array()
gOkCollide = ({})
gChain = ""
gMinNo = 1
gMaxNo = 9999
gCargoSet = ({})
gMovingSet = ({})
gBusy = false
gSCidx = -1
gSCcircle = -1
gSCpt = {0 0 0}
gTargetPt = {0 0 0}
gNewDrag = false
gTow = false
g1dynamicIdx = -1
g2dynamicIdx = -1
gSCcheck = true
gBondPicking = false
gFreeze = array()
gToab = false


function get_cp_idx (idx) {
    var no = {atomIndex=idx}.atomno
    while ((no < gMaxNo) and ({(atomno=no) and (chain=gChain)
        and thisModel}.atomName != "C")) {
        no++
    }
    return ({(atomno=no) and (chain=gChain) and thisModel}.atomIndex)
}

function get_cm_no (iNo) {
    while ((iNo > 0) and ({(atomno=iNo) and (chain=gChain)
        and thisModel}.atomName != "C")) {
        iNo--
    }
    return iNo
}

function get_nm_idx (idx) {
    var no = {atomIndex=idx}.atomno
    while ((no > 0) and ({(atomno=no) and (chain=gChain)
        and thisModel}.atomName != "N")) {
        no--
    }
    return ({(atomno=no) and (chain=gChain) and thisModel}.atomIndex)
}

function get_np_no (iNo) {
    while ((iNo < gMaxNo) and ({(atomno=iNo) and (chain=gChain)
        and thisModel}.atomName != "N")) {
        iNo++
    }
    return iNo
}

function get_cb_idx (BBidx) {
    var no = {atomIndex=BBidx}.atomno
    var i = 1
    for (; i < 5; i++) {
        if ({(atomno=@{no+i}) and (chain=gChain)
            and thisModel}.atomName == "CB") {
            break
        }
    }
    return {(atomno=@{no+i}) and (chain=gChain) and thisModel}.atomIndex
}

function get_o_idx (BBidx) {
    var no = {atomIndex=BBidx}.atomno
    var i = 1
    for (; i < 4; i++) {
        if ({(atomno=@{no+i}) and (chain=gChain)
            and thisModel}.atomName == "O") {
            break
        }
    }
    return {(atomno=@{no+i}) and (chain=gChain) and thisModel}.atomIndex
}

function get_nward_bb_idx (idx, iChain) {
    var no = {atomIndex=idx}.atomno - 1
    for (; no >= 0; no--) {
        var atomName = {(atomno=no) and (chain=iChain)
            and thisModel}.atomName
        if ((atomName = "N") or (atomName = "C") or (atomName = "CA")) {
            break
        }
    }
    return ((no >= 0) ? ({(atomno=no) and (chain=iChain)
        and thisModel}.atomIndex) : -1)
}

function get_cward_bb_idx (idx, iChain) {
    var no = {atomIndex=idx}.atomno + 1
    for (; no < gMaxNo; no++) {
        var atomName = {(atomno=no) and (chain=iChain)
            and thisModel}.atomName
        if ((atomName = "N") or (atomName = "C") or (atomName = "CA")) {
            break
        }
    }
    return ((no >= 0) ? ({(atomno=no) and (chain=iChain)
        and thisModel}.atomIndex) : -1)
}

function get_sc_set (scIdx, iChain) {
    var scSet = ({})
    var idx = get_sc_bb_idx(scIdx, iChain)
    var iNo = {atomIndex=idx}.atomno + 3

    for (var i = 1; i < 20; i++) {
        idx = {(atomno=@{iNo+i}) and (chain=iChain)
            and thisModel}.atomIndex
        if (is_bb_idx(idx)) {
            break
        }
        scSet = scSet or {atomIndex=idx}
    }
    return scSet
}

function get_sc_bb_idx (idx, iChain) {
    var no = {atomIndex=idx}.atomno
    for (; no > 0; no--) {
        if ({(atomno=no) and (chain=iChain)
            and thisModel}.atomName == "CA") {
            break
        }
        else if ({(atomno=no) and (chain=iChain)
            and thisModel}.atomName == "C") {
            break
        }
        else if ({(atomno=no) and (chain=iChain)
            and thisModel}.atomName == "N") {
            break
        }
        else if ({(atomno=no) and (chain=iChain)
            and thisModel}.atomName == "CB") {
            no -= 3
            break
        }
    }
    return {(atomno=no) and (chain=iChain) and thisModel}.atomIndex
}

function is_bb_idx(aIdx) {
    var ret = false
    switch({atomIndex=aIdx}.atomName) {
    case "N":
    case "CA":
    case "C":
        ret = true
        break
    }
    return ret
}

function is_sc_idx(aIdx) {

    var ret = false
    if (not is_bb_idx(aIDx)) {

        ret = true
        switch({atomIndex=aIdx}.atomName) {
        case "O":
        case "CB":
            ret = false
            break
        }
    }
    return ret
}

function select_add_sc(fromIdx) {
    var iNo = {atomIndex=fromIdx}.atomno
    var iChain = {atomIndex=fromIdx}.chain
    select none
    while ({(atomno=iNo) and (chain=iChain)
        and thisModel and sidechain}) {
        var a = {(atomno=iNo) and (chain=iChain)and thisModel}
        a.selected = true
        iNo++
    }
}

# Resolve collisions on selection
function handle_collisions( targetIdx) {

    # For all selected atoms
    for (var iNo = {selected}.min.atomno; iNo <= {selected}.max.atomno; iNo++) {
        var idx = {(atomno=iNo) and (chain=gchain)
            and thisModel}.atomIndex
        if ({atomindex=idx}.selected) {

            # Collect local colliders
            var lcAtoms = (within(kCtolerance, false, {atomIndex=idx})
                and not {atomIndex=idx}
                and not {gOkCollide}
                and not connected({atomIndex=idx}))
            if (lcAtoms) {
                # Ignore kinked BB
                try {
                    if (is_bb_idx(idx) and (angle(
                        {atomIndex=@{get_cward_bb_idx(idx, gChain)}}, {atomIndex=idx},
                        {atomIndex=@{get_nward_bb_idx(idx, gChain)}}) < 100.0)) {
                        continue
                    }
                }
                catch {
                }

                # For all local colliders
                for (var c = 1; c <= lcAtoms.size; c++ ) {
                    var cidx = lcAtoms[c].atomIndex

                    # If it is with water, delete it
                    if (lcAtoms[c].group = "HOH") {
                        delete {atomIndex=cidx}
                    }

                    # else if it is with side chain not proline, fix it
                    else if (is_sc_idx(cidx) and ({atomIndex=cidx}.group != "PRO")) {
                        fix_sc_collision_2(cidx)
                        
                        # If not fixed, exit fail
                        if (not gOk2) {
                            return # early exit (break n jmol bug)
                        }
                    }

                    # else if it is itself a side chain not proline, fix it
                    else if (is_sc_idx(idx) and ({atomIndex=idx}.group != "PRO")) {
                        fix_sc_collision_2(idx)

                        # If not fixed, exit fail
                        if (not gOk2) {
                            return # early exit (break n jmol bug)
                        }
                    }

                    # Else if it is with O, counter-rotate
                    else if (lcAtoms[c].atomName = "O") {
                        counter_rotate_2(lcAtoms[c].atomIndex,
                            {atomIndex=idx}.xyz, targetIdx, false)

                        # If not fixed, exit fail
                        if (not gOk2) {
                            return # early exit (break n jmol bug)
                        }
                    }

                    # Else if it is itself O, counter-rotate
                    else if ({atomIndex=idx}.atomName = "O") {
                        counter_rotate_2(idx, lcAtoms[c].xyz, targetIdx, false)

                        # If not fixed, exit fail
                        if (not gOk2) {
                            return # early exit (break n jmol bug)
                        }
                    }

                    else {    # Else not fixed, exit fail
                        gOk2 = false
                        return # early exit (break n jmol bug)
                    }
                } # endfor
            }
        }
    } # endfor iNo
}

# Rotate rotor set to move target atom to its proper place
function tug_track_idx(targetIdx, targetPt, nWard, cDetect) {
    gOK = false
    var pt = targetPt
    var dist = distance(pt, {atomIndex=targetIdx}.xyz)

    var rotors = (nWard ? gNrotors : gCrotors)

    # For a number of passes
    for (var pass1 = 0; pass1 < 20; pass1++) {
        var blocked = ({})
        for (var pass2 = 0; pass2 < (rotors.size/4); pass2++) {

            var v1 = {atomIndex=targetIdx}.xyz - pt

            # Find the most orthgonal unused rotor
            var imax = 0
            var smax = 0.5
            for (var i = 1; i < rotors.size; i += 4) {
                var i2 = rotors[i+1]
                var i3 = rotors[i+2]
                var i4 = rotors[i+3]
                if ((i2 != targetIdx) and (i3 != targetIdx) and (i4 != targetIdx)) {
                    if ({blocked and {atomIndex=i2}}.count == 0) {
                        var v2 = {atomIndex=i3}.xyz - {atomIndex=i2}.xyz

                        var s = sin(abs(angle(v1, {0 0 0}, v2)))
                        if (s > smax) {
                            smax = s
                            imax = i
                        }
                    }
                }
            }

            # If no more rotors, break to next full try
            if (imax == 0) {
               break
            }
            var i1 = rotors[imax+0]
            var i2 = rotors[imax+1]
            var i3 = rotors[imax+2]
            var i4 = rotors[imax+3]

            # Get dihedral of rotor with target point
            var dt = angle({atomIndex=targetIdx}, {atomIndex=i2}, {atomIndex=i3}, pt)
            var dh = angle({atomIndex=i1}, {atomIndex=i2}, {atomIndex=i3}, {atomIndex=i4})
            if (abs(dh).type == "string") {
                dh = -50
            }
            var psi = dh + dt
            var phi = dh + dt

            # Compute resultant psi and phi
            #  and select from target atom to first half of rotor
            var movePt = false
            if (nWard) {
                if ({atomIndex=i2}.atomName="CA") {
                    psi = angle({atomIndex=@{get_cward_bb_idx(i1, gChain)}}, {atomIndex=i1},
                        {atomIndex=i2}, {atomIndex=i3}) + dt
                }
                else {
                    phi = angle({atomIndex=i1}, {atomIndex=i2},
                        {atomIndex=i3}, {atomIndex=@{get_nward_bb_idx(i3, gChain)}}) + dt
                }

                if ({atomIndex=i2}.atomno > {atomIndex=targetIdx}.atomno) {
                    movePt = true
                    select_nward_idx(i3, get_cward_bb_idx(targetIdx, gChain))
                    {atomIndex=targetIdx}.selected = true
                }
                else {
                    select_cward_idx(i2, targetIdx)
                }
            }
            else {
                if (({atomIndex=i2}.atomName="CA")) {
                    phi = angle({atomIndex=@{get_nward_bb_idx(i1, gChain)}}, {atomIndex=i1},
                        {atomIndex=i2}, {atomIndex=i3}) + dt
                }
                else {
                    psi = angle({atomIndex=i2}, {atomIndex=i3},
                        {atomIndex=i4}, {atomIndex=@{get_cward_bb_idx(i4, gChain)}}) + dt
                }

                if ({atomIndex=i2}.atomno < {atomIndex=targetIdx}.atomno) {
                    movePt = true
                    select_cward_idx(i3, get_nward_bb_idx(targetIdx, gChain))
                    {atomIndex=targetIdx}.selected = true
                }
                else {
                    select_nward_idx(i2, targetIdx)
                }
            }

            # Relax rules if desperate
            if (pass1 > 10) {
                phi = -50
            }

            # If rotation within ramachandran limits
            if ((abs(dt) >= 0.1) and
                (({atomIndex=i2}.group=="GLY") or (phi < 0))) {

                # If moving target point, put the target atom there
                var cp = {atomIndex=targetIdx}.xyz
                if (movePt) {
                    dt = -dt
                    {atomIndex=targetIdx}.xyz = pt
                }

                # Rotate to minimize vector ====================
                rotateSelected {atomIndex=i2} {atomIndex=i3} @dt

                # If collision checking
                if (cDetect) {

                    # If collision, back off by eighths
                    var wasCollision = false
                    for (var ci = 0; ci < 4; ci++) {
                        if (ci < 3) {
                            dt /= 2
                        }
                        handle_collisions( targetIdx)
                        if (not gOk2) {
                            wasCollision = true
                            rotateSelected {atomIndex=i2} {atomIndex=i3} @{-dt}
                        }
                        else if (wasCollision) {
                            if (ci <3) {
                                rotateSelected {atomIndex=i2} {atomIndex=i3} @{dt}
                            }
                        }
                        else {
                            break
                        }

                        if (dt < 0.01) {
                            break
                        }
                    } # endfor
                }

                # If moving target point, put the target atom back
                if (movePt) {
                    pt = {atomIndex=targetIdx}.xyz
                    {atomIndex=targetIdx}.xyz = cp
                }

            }

            # If close enough, stop
            if (distance(pt, {atomIndex=targetIdx}) < kDtolerance) {
                gOK = true
                gTargetPt = pt
                break
            }

            # Block rotor
            blocked |= {atomIndex=i2}

        }   # endfor num rotors passes

        if (gOK) {
            break
        }
    }   # endfor 20 passes
}

# Counter rotate bonds on either side of a BB O
function do_counter_rotate(caPhiIdx, nIdx, cIdx, oIdx, caPsiIdx, dir, nWard) {

    # Rotate psi
    {atomIndex=nIdx}.selected = nWard
    {atomIndex=cIdx}.selected = nWard
    {atomIndex=oIdx}.selected = nward
    rotateSelected {atomIndex=caPsiIdx} {atomIndex=cIdx} @{dir}

    # Counter-rotate phi
    {atomIndex=nIdx}.selected = not nWard
    {atomIndex=cIdx}.selected = not nWard
    {atomIndex=oIdx}.selected = not nward
    rotateSelected {atomIndex=nIdx} {atomIndex=caPhiIdx} @{-dir}
}

function counter_rotate(oIdx, dir, nWard) {

    var iChain = {atomIndex=oIdx}.chain
    var selsave = {selected}
    var cIdx = get_sc_bb_idx(oIdx, iChain)
    var nIdx = get_cward_bb_idx(cIdx, iChain)
    var caPhiIdx = get_cward_bb_idx(nIdx, iChain)
    var caPsiIdx = get_nward_bb_idx(cIdx, iChain)

    if (nWard) {
        var nNo = {(chain=iChain) and thisModel}.atomno.min
        select_nward_idx(caPsiIdx, {(atomno=nNo) and (chain=iChain)
            and thisModel}.atomIndex)
    }
    else {
        var cNo = {(chain=iChain) and thisModel}.atomno.max
        select_cward_idx(caPhiIdx, {(atomno=cNo) and (chain=iChain)
            and thisModel}.atomIndex)
    }

    # Counter-rotate
    do_counter_rotate(caPhiIdx, nIdx, cIdx, oIdx, caPsiIdx, dir, not nWard)
    select selsave
}

function counter_rotate_2(oIdx, toPt, terminalIdx, oDrag) {

    var iChain = {atomIndex=oIdx}.chain
    var selsave = {selected}
    var gOk2 = true
    var cIdx = get_sc_bb_idx(oIdx, iChain)
    var nIdx = get_cward_bb_idx(cIdx, iChain)
    var caPhiIdx = get_cward_bb_idx(nIdx, iChain)
    var caPsiIdx = get_nward_bb_idx(cIdx, iChain)

    var nTward = ({atomIndex=oIdx}.atomno < {atomIndex=terminalIdx}.atomno)
    if (nTward) {
        select_cward_idx(cIdx, terminalIdx)
    }
    else {
        select_nward_idx(nIdx, terminalIdx)
    }

    # Until all collisions cancelled
    var dir = 5
    var ang = angle(toPt, {atomIndex=oIdx}, {atomIndex=cIdx})
    var tcount = 0
    while (oDrag or (within(kCtolerance, false, {atomIndex=oIdx})
            and not {atomIndex=oIdx} and not connected({atomIndex=oIdx})
            and not {gOkCollide} > 0)) {

        # Counter-rotate
        do_counter_rotate(caPhiIdx, nIdx, cIdx, oIdx, caPsiIdx, dir, nTward)
        var newang = angle(toPt, {atomIndex=oIdx}, {atomIndex=cIdx})

        # If wrong direction once, undo and reverse
        if (newang > ang) {
            do_counter_rotate(caPhiIDx, nIdx, cIdx, oIdx, caPsiIdx, -dir, nTward)

            # If first time, continue in opposite direction
            dir *= -1
            if (dir < 0) {
                continue
            }
        }

        if (oDrag) {
            break
        }

        # If no go, undo and exit
        tcount++
        if (tcount > (360/abs(dir))) {
            gOk2 = false
            break
        }

    } # endwhile
    select selsave
}

# Repair proline
function repair_proline(BBidx) {
    var cbidx = get_cb_idx(BBidx)
    var cbno = {atomIndex=cbidx}.atomno
    var cgidx = {(atomno=@{cbno+1}) and (chain=gChain)
        and thisModel}.atomIndex
    var cdidx = {(atomno=@{cbno+2}) and (chain=gChain)
        and thisModel}.atomIndex
    var caidx = {(atomno=@{cbno-3}) and (chain=gChain)
        and thisModel}.atomIndex
    var nidx = {(atomno=@{cbno-4}) and (chain=gChain)
        and thisModel}.atomIndex

    select {atomIndex=cbidx}
    set_angle_idx(nidx, caidx, cbidx, 109.5)

    select {atomIndex=cdidx}
    set_distance_idx(nidx, cdidx, 1.47)
    set_angle_idx(caidx, nidx, cdidx, 102.7)
    set_dihedral_idx(cbidx, caidx, nidx, cdidx, 16.2)

    select {atomIndex=cgidx}
    set_distance_idx(cdidx, cgidx, 1.51)
    set_angle_idx(nidx, cdidx, cgidx, 106.4)
    set_dihedral_idx(caidx, nidx, cdidx, cgidx, 16.2)
}

# Repair side chain
function repair_sc(targetIdx, nWard) {

    var idx = (nWard ? get_cward_bb_idx(targetIdx, gChain) : get_nward_bb_idx(targetIdx, gChain))

    if (({atomIndex=targetIdx}.atomName == "CA")
        and ({atomIndex=targetIdx}.group != "GLY")) {
        var cbidx = get_cb_idx(targetIdx)
        select none
        select_add_sc(cbidx)
        set_angle_idx(idx, targetIdx, cbidx, 110.0)
        set_distance_idx(targetIdx, cbidx, 1.5)
        if ({atomIndex=targetIdx}.group != "PRO") {
            var colliders = (within(kCtolerance, false, {selected})
                and not {atomIndex=targetIdx} and not {selected})
            if (colliders) {
                if ({atomIndex=targetIdx}.group != "ALA") {
                    fix_sc_collision_2(cbidx)
                }
            }
        }
        else {
            if (nWard) {
            }
            else {
                set_dihedral_idx(get_nward_bb_idx(idx, gChain), idx, targetIdx, cbidx, 174.2)
            }
        }
    }

    else if ({atomIndex=targetIdx}.atomName == "C") {
        var oidx = get_o_idx(targetIdx)
        select {atomIndex=oidx}
        set_angle_idx(idx, targetIdx, oidx, 120.0)
        set_distance_idx(targetIdx, oidx, 1.21)
        if (nWard) {
            set_dihedral_idx(get_cward_bb_idx(idx, gChain), idx, targetIdx, oidx, 0.0)
        }
        if ({atomIndex=idx}.group == "PRO") {
            repair_proline(idx)
            var dNo = {atomIndex=targetIdx}.atomno + 4
            var dIdx = {(atomno=dNO) and (chain=gChain)
                and thisModel}.atomIndex
            var colliders = (within(kCtolerance, false, {atomIndex=dIdx})
                and not connected({atomIndex=dIdx})
                and not {atomIndex=dIdx})
            for (var i = 1; i <= colliders.size; i++) {
                if (colliders[i].atomName == "O") {
                    counter_rotate_2(colliders[i].atomIndex,
                        {atomIndex=dIdx}.xyz, targetIdx, false)
                }
            }
        }
    }
}

# Rebuild Cward rotors set
function tug_track_c() {

    # For all bb atoms cWard of cargo
    var targetIdx = gCcargoIdx
    var okCount = 0

    # Allow collisions with cargo
    gOkCollide = gCargoSet
    var tcount = 0
    while (targetIdx != gNanchorIdx) {

        # Step to next atom
        targetIdx = get_cward_bb_idx(targetIdx, gChain)
        if (targetIdx < 0) {
            break
        }

        # No collision with cargo allowed after two atoms placed
        if (tcount == 2) {
           gOkCollide = ({})
        }
        tcount++

        # Compute targets desired coords
        try {
            var c1idx = get_cward_bb_idx(targetIdx, gChain)
            var n1idx = get_nward_bb_idx(targetIdx, gChain )
            var n2idx = get_nward_bb_idx(n1Idx, gChain)
            var n3idx = get_nward_bb_idx(n2Idx, gChain)
            var pt = {0 0 0}
            if ({atomIndex=targetIdx}.atomName == "N") {
                var oidx = get_o_idx(n1idx)
                select {atomIndex=oidx}
    
                # Desired target location is trigonal O
                set_distance_idx(n1idx, oidx, 1.5)
                pt = get_trigonal_idx(n2idx, n1idx, oidx, 1.37)
                set_distance_idx(n1idx, oidx, 1.21)
            }
            else if (({atomIndex=targetIdx}.atomName == "C")
                and ({atomIndex=targetIdx}.group != "GLY")) {
    
                # Desired target location is tetragonal CB
                var cbidx = get_cb_idx(n1idx)
                pt = get_tet_idx(n2idx, n1idx, cbidx, 1.5)
            }
            else { # CA (or GLY C)
    
                # Save current target coords
                var cp = {atomIndex=targetIdx}.xyz
    
                # Set target atom at desired distance and angle
                select {atomIndex=targetIdx}
                set_distance_idx(n1idx, targetIdx, 1.5)
                set_angle_idx(n2idx, n1idx, targetIdx, 120.0)
                if ({atomIndex=targetIdx}.atomName == "CA") {
                    set_dihedral_idx(n3idx, n2idx, n1idx, targetIdx, 180)
                }
    
                # Record and restore target
                pt = {atomIndex=targetIdx}.xyz
                {atomIndex=targetIdx}.xyz = cp
            }
    
            # If target not at desired location
            if (distance(pt, {atomIndex=targetIdx}) > kDtolerance) {
                okCount = 0
                gTargetPt = pt
                var xcount = 0
                gOK = false
                while ((xcount < 20) and (not gOK)) {
    
                    # Rotate on cWard rotor set to move it there
                    tug_track_idx(targetIdx, pt, false, false)
                    xcount++
                }
            }
            else {
                gOK = true
                okCount++
            }
        }
        catch {
        }

        # If successful
        if (gOK == true) {

            # Adust any side atoms
            repair_sc(targetIdx, false)
        }

        # Else fail
        else {
            break
        }

        # If no movement in 4 tries, we are done
        if (okCount > 3) {
            break
        }
    } # endwhile (targetIdx != gCanchorIdx) {
}

# Rebuild Nward rotors set
function tug_track_n() {

    gOK = true

    # For all bb atoms nWard of cargo
    var targetIdx = gNcargoIdx
    var okCount = 0

    # Allow collisions with cargo
    gOkCollide = gCargoSet
    var tcount = 0
    while (targetIdx != gNanchorIdx) {

        # Step to next atom
        targetIdx = get_nward_bb_idx(targetIdx, gChain)
        if (targetIdx < 0) {
            break
        }
        
        # No collision with cargo allowed after two atoms placed
        if (tcount == 2) {
           gOkCollide = ({})
        }
        tcount++

        # Compute targets desired coords
        var n1idx = get_nward_bb_idx(targetIdx, gChain)
        var c1idx = get_cward_bb_idx(targetIdx, gChain)
        var c2idx = get_cward_bb_idx(c1idx, gChain)
        var c3idx = get_cward_bb_idx(c2idx, gChain)
        var pt = {0 0 0}
        if ({atomIndex=targetIdx}.atomName == "CA") {

            # Desired target location is trigonal O
            var oidx = get_o_idx(c1idx)
            select {atomIndex=oidx}
            set_distance_idx(c1idx, oidx, 1.39)
            pt = get_trigonal_idx(c2idx, c1idx, oidx, 1.41)
            set_distance_idx(c1idx, oidx, 1.21)
        }
        else if (({atomIndex=targetIdx}.atomName == "N")
            and ({atomIndex=targetIdx}.group != "GLY")) {

            # Desired target location is r-tetragonal CB
            var cbidx = get_cb_idx(c1idx)
            pt = get_tet_idx(cbidx, c1idx, c2idx, 1.5)
        }
        else { # C

            # Save current target coords
            var cp = {atomIndex=targetIdx}.xyz

            # Set target atom at desired distance and angle
            select {atomIndex=targetIdx}
            set_distance_idx(c1idx, targetIdx, 1.37)
            set_angle_idx(c2idx, c1idx, targetIdx, 110.0)

            if ({atomIndex=targetIdx}.group == "PRO") {
                set_dihedral_idx(c3idx, c2idx, c1idx, targetIdx, -57.0)
            }

            # Record and restore target
            pt = {atomIndex=targetIdx}.xyz
            {atomIndex=targetIdx}.xyz = cp
        }

        # If target not at desired location
        if (distance(pt, {atomIndex=targetIdx}) > kDtolerance) {
            var okCount = 0
            gTargetPt = pt
            var xcount = 0
            gOK = false
            while ((xcount < 20) and (not gOK)) {

                # Rotate on nWard rotor set to move it there
                tug_track_idx(targetIdx, pt, true, false)
                xcount++
            }
        }
        else {
            gOK = true
            okCount++
        }

        # If sucessful
        if (gOK == true) {

            # Adust any side atoms
            repair_sc(targetIdx, true)
        }

        # Else fail
        else {
            break
        }

        # If no movement in 4 tries, we are done
        if (okCount > 3) {
            break
        }

    }   # endwhile (targetIdx != gNanchorIdx) {
}

# gPlicoRecord is maintained by the macro pilcoRecord
function translate_selected_record(pt) {
    if (gPlicoRecord != "") {
        plico_record(format("select %s;translateSelected %s;", {selected}, pt))
    }
    translateSelected @pt
}

# gPlicoRecord is maintained by the macro pilcoRecord
function rotate_selected_record(pivotIdx, caxis, a) {
    if (gPlicoRecord != "") {
        plico_record(format("select %s;", {selected}))
        plico_record(format("rotateSelected {atomIndex=%d} @%s @%s;",
            pivotIdx, caxis, a))
    }
    rotateSelected {atomIndex=pivotIdx} @caxis @a
}

function collect_sc_rotors(no, iChain) {
    var scBondIdxs = array()
    for (var iNo = no; iNo >= 0; iNo--) {
        var ile = 0
        switch ({(atomno=iNo) and (chain=iChain)
            and thisModel}.atomName) {
        case "CA" :
            return scBondIdxs # Early exit since break 1 appears broken
        case "CZ" :
            if ({(atomno=iNo) and (chain=iChain)
                and thisModel}.group == "TYR") {
                break
            }
        case "CE" :
            if ({(atomno=iNo) and (chain=iChain)
                and thisModel}.group == "MET") {
                break
            }
        case "CG1" :
            if ({(atomno=iNo) and (chain=iChain)
                and thisModel}.group == "VAL") {
                break
            }
            if ({(atomno=iNo) and (chain=iChain)
                and thisModel}.group == "ILE") {
                ile = 1
            }
        case "NE" :
        case "CD" :
        case "SD" :
        case "CG" :
        case "CB" :
            scBondIdxs += {(atomno=@{iNo+1+ile}) and (chain=iChain)
                and thisModel}.atomIndex
            scBondIdxs += {(atomno=@{iNo+0}) and (chain=iChain)
                and thisModel}.atomIndex
            if ({(atomno=iNo) and (chain=iChain)
                and thisModel}.atomName%2 == "CG") {
                scBondIdxs += {(atomno=@{iNo-1}) and (chain=iChain)
                    and thisModel}.atomIndex
                scBondIdxs += {(atomno=@{iNo-4}) and (chain=iChain)
                    and thisModel}.atomIndex
            }
            else if ({(atomno=iNo) and (chain=iChain)
                and thisModel}.atomName == "CB") {
                scBondIdxs += {(atomno=@{iNo-3}) and (chain=iChain)
                    and thisModel}.atomIndex
                scBondIdxs += {(atomno=@{iNo-4}) and (chain=iChain)
                    and thisModel}.atomIndex
            }
            else {
                scBondIdxs += {(atomno=@{iNo-1}) and (chain=iChain)
                    and thisModel}.atomIndex
                scBondIdxs += {(atomno=@{iNo-2}) and (chain=iChain)
                    and thisModel}.atomIndex
            }
            break
        }

    }

    return scBondIdxs
}

# Drag Side Chain
function drag_sc() {
    var iNo = {atomIndex=gSCidx}.atomno
    var iChain  = {atomIndex=gSCidx}.chain
    if ({atomIndex=gSCidx}.group != "PRO") {

        var scBondIdxs = collect_sc_rotors( iNo, iChain)
        var numChi = scBondIdxs.size / 4
        var dist = distance({atomIndex=gSCidx}.xyz, gSCpt)
        var scSet = get_sc_set(gSCidx, iChain)

        # For all rotor combinations
        var dh = array()
        for (var i = 0; i < numChi; i++) {
            dh += angle({atomIndex=@{scBondIdxs[4+(4*i)]}},
                {atomIndex=@{scBondIdxs[3+(4*i)]}},
                {atomIndex=@{scBondIdxs[2+(4*i)]}},
                {atomIndex=@{scBondIdxs[1+(4*i)]}})
        }
        for (var i = 0; i < numChi; i++) {
            var rot = -120
            for (var j = 0; j < 6; j++) {
                rot += 60*j
                select_add_sc(scBondIdxs[1+(4*i)])
                set_dihedral_idx(scBondIdxs[4+(4*i)], scBondIdxs[3+(4*i)],
                    scBondIdxs[2+(4*i)], scBondIdxs[1+(4*i)], rot)
                var newDist = distance({atomIndex=gSCidx}.xyz, gSCpt)
                if (gSCcheck) {
                    var colliders = (within(kCtolerance, false, scSet)
                        and not connected(scSet) and not {scSet})
                    if (colliders) {
                        continue
                    }
                }

                # Find the best
                if (newDist < dist) {
                    dist = newDist
                    for (var k = 0; k < numChi; k++) {
                        dh[k+1] = angle({atomIndex=@{scBondIdxs[4+(4*k)]}},
                        {atomIndex=@{scBondIdxs[3+(4*k)]}},
                        {atomIndex=@{scBondIdxs[2+(4*k)]}},
                        {atomIndex=@{scBondIdxs[1+(4*k)]}})
                    }
                }
            }
        }

        # Now set the best
        for (var i = 0; i < numChi; i++) {
            select_add_sc(scBondIdxs[1+(4*i)])
            set_dihedral_idx(scBondIdxs[4+(4*i)], scBondIdxs[3+(4*i)],
                scBondIdxs[2+(4*i)], scBondIdxs[1+(4*i)], dh[i+1])
        }
        
        if (gSCcheck = false) {
            plico_minimize( scSet)
        }
    }
    else { # PRO - toggle between puckers up and down
        var icd = {(atomno=@{iNo+1}) and (chain=iChain)
            and thisModel}.atomIndex
        var icb = {(atomno=@{iNo-1}) and (chain=iChain)
            and thisModel}.atomIndex
        var ica = {(atomno=@{iNo-4}) and (chain=iChain)
            and thisModel}.atomIndex
        var in = {(atomno=@{iNo-5}) and (chain=iChain)
            and thisModel}.atomIndex
        select {atomIndex=gSCidx}

        if (angle({atomIndex=ica}, {atomIndex=in},
            {atomIndex=icd}, {atomIndex=gSCidx}) < -10.0) {
            set_dihedral_idx(ica, in, icd, gSCidx, 8.7)
            set_angle_idx(in, icd, gSCidx, 110.0)
            set_distance_idx(icd, gSCidx, 1.5)
        }
        else {
            set_dihedral_idx(ica, in, icd, gSCidx, -29.5)
            set_angle_idx(in, icd, gSCidx, 108.8)
            set_distance_idx(icd, gSCidx, 1.5)
        }
    }

    draw gSCcircle CIRCLE {atomIndex=gSCidx} MESH NOFILL
    gSCpt = {atomIndex=gSCidx}.xyz
}

# Fix side chain collisions
function fix_sc_collision_2(idx) {
    gOk2 = false
    var iNo = {atomIndex=idx}.atomno
    var iChain = {atomIndex=idx}.chain
    var resno = {(atomno=iNo) and (chain=iChain)
        and thisModel}.resno

    # Get SC terminus
    while (resno == {(atomno=iNo) and (chain=iChain)
        and thisModel}.resno) {
        iNo++
    }
    iNo--

    var sc = array()
    var iBno = iNo
    while ({(atomno=iBno) and (chain=iChain)
        and thisModel}.atomName != "CB") {
        sc += {(atomno=iBno) and (chain=iChain)
            and thisModel}
        iBno--
    }
    var cbidx = {(atomno=iBno) and (chain=iChain)
        and thisModel}.atomIndex

    var scBondIdxs = collect_sc_rotors( iNo, iChain)
    var numChi = scBondIdxs.size / 4

    # For all rotor combinations
    for (var i = 0; i < numChi; i++) {
        var rot = -120
        for (var j = 0; j < 6; j++) {
            rot += 60
            select_add_sc(scBondIdxs[1+(4*i)])
            set_dihedral_idx(scBondIdxs[1+(4*i)], scBondIdxs[2+(4*i)],
                scBondIdxs[3+(4*i)], scBondIdxs[4+(4*i)], rot)

            # If no collision, exit
            var colliders = (within(kCtolerance, false, {sc})
                and not {atomIndex=cbidx} and not {sc})

            # If it is with water, delete the water
            for (var c = 1; c < colliders.size; c++ ) {
                if (colliders[c].group = "HOH") {
                    delete {atomIndex=@{colliders[c].atomIndex}}
                    colliders = {colliders and not @{colliders[c]}}
                }
            }

            if (colliders.size == 0) {
                gOk2 = true
                return # Early exit since break 1 appears broken
            }

        }
    }
}

function is_moveable_sc(aIdx) {

    var ret = (({atomIndex=aIdx}.group != "PRO")
        or ({atomIndex=aIdx}.atomName == "CG"))
    switch({atomIndex=aIdx}.atomName) {
    case "N":
    case "CA":
    case "C":
    case "CB":
    case "O":
    case "O4\'":
        ret = false
        break
    }
    return ret
}

function is_rotor_avail(i1idx, i2idx) {
    var ret = true
    if (i1idx > i2idx) { # frozen bonds are kept as lo-hi
        var idx = @i1idx
        i1idx = @i2idx
        i2idx = @idx
    }

    for (var i = 1; i <= gFreeze.size; i += 2) {
        if ((gFreeze[i] == i1idx) and (gFreeze[i+1] == i2idx)) {
            ret = false
            break
        }
    }

    return ret
}

function xcollect_bb_rotors(nWard) {
    var anchorNo = (nWard
        ? ((gNanchorIdx >= 0) ? {atomIndex=gNanchorIdx}.atomno : gMinNo)
        : ((gCanchorIdx >= 0) ? {atomIndex=gCanchorIdx}.atomno : gMaxNo))
    var cargoNo = (nWard
        ? ((gNcargoIdx >= 0) ? {atomIndex=gNcargoIdx}.atomno
        : {atomIndex=gCcargoIdx}.atomno)
        : {atomIndex=gCcargoIdx}.atomno)
    var rotors = array()
    if (cargoNo < anchorNo) {

        for (var iNo = cargoNo; iNo <= anchorNo; iNo++) {
            if ({(atomno=iNo) and (chain=gChain)
                and thisModel}.atomName == "CA") {
                if (is_rotor_avail(iNo)) {# xxx
                    if (({(atomno=iNo) and (chain=gChain)
                        and thisModel}.group != "PRO") and (iNo > cargoNo)) { # phi
                        rotors += [{(atomno=@{get_cm_no(iNo-1)}) and (chain=gChain)
                            and thisModel}.atomIndex,
                            {(atomno=@{iNo-1}) and (chain=gChain)
                                and thisModel}.atomIndex]
                        rotors += [{(atomno=@{iNo}) and (chain=gChain)
                            and thisModel}.atomIndex,
                            {(atomno=@{iNo+1}) and (chain=gChain)
                                and thisModel}.atomIndex]
                    }
                    if (iNo != (anchorNo-1)) { # psi
                        rotors += [{(atomno=@{iNo-1}) and (chain=gChain)
                            and thisModel}.atomIndex,
                            {(atomno=@{iNo}) and (chain=gChain)
                                and thisModel}.atomIndex]
                        rotors += [{(atomno=@{iNo+1}) and (chain=gChain)
                            and thisModel}.atomIndex,
                            {(atomno=@{get_np_no(iNo+2)}) and (chain=gChain)
                                and thisModel}.atomIndex]
                    }
                }
            }
        }
    }
    else {

        for (var iNo = cargoNo; iNo >= anchorNo; iNo--) {
            if ({(atomno=iNo) and (chain=gChain)
                and thisModel}.atomName == "CA") {
                if (is_rotor_avail(iNo)) {
                    if ((iNo != (anchorNo-1)) and (iNo < cargoNo)) { # psi
                        rotors += [{(atomno=@{get_np_no(iNo+2)}) and (chain=gChain)
                            and thisModel}.atomIndex,
                            {(atomno=@{iNo+1}) and (chain=gChain)
                                and thisModel}.atomIndex]
                        rotors += [{(atomno=@{iNo}) and (chain=gChain)
                            and thisModel}.atomIndex,
                            {(atomno=@{iNo-1}) and (chain=gChain)
                                and thisModel}.atomIndex]
                    }
                    if ({(atomno=iNo) and (chain=gChain)
                        and thisModel}.group != "PRO") { # phi
                        rotors += [{(atomno=@{iNo+1}) and (chain=gChain)
                            and thisModel}.atomIndex,
                            {(atomno=@{iNo}) and (chain=gChain)
                                and thisModel}.atomIndex]
                        rotors += [{(atomno=@{iNo-1}) and (chain=gChain)
                            and thisModel}.atomIndex,
                            {(atomno=@{get_cm_no(iNo-1)}) and (chain=gChain)
                                and thisModel}.atomIndex]
                    }
                }
            }
        }
    }

    if (nWard) {
        gNrotors = rotors
    }
    else {
        gCrotors = rotors
    }
}
function collect_bb_rotors(nWard) {
    if (nWard) {
        gNrotors = array()
        var nres = {atomIndex=gNcargoIdx}.resno
        var nname = {atomIndex=gNcargoIdx}.atomName
        var ares = get_resno_min(gChain)
        var aA = get_atom_rcn( ares, gChain, "N")
        if (gNanchorIdx >= 0) {
            aA = {atomIndex=gNanchorIdx}
        }
        var aname = aA.atomName
        for (var r = nres; r >= ares; r--) {
            var aCp = get_atom_rcn( r-1, gChain, "C")
            var aN = get_atom_rcn( r, gChain, "N")
            var aCa = get_atom_rcn( r, gChain, "CA")
            var aC = get_atom_rcn( r, gChain, "C")
            var aNn = get_atom_rcn( r+1, gChain, "N")
            if (aCp.size < 1) {
                aCp = aNn
            }
            # psi
            if (((r < nres) and (r > ares))
                or ((r == nres) and (nname == "C"))
                or ((r == ares) and (aname != "C"))) {
                if (is_rotor_avail(aCa.atomIndex, aC.atomIndex)) {
                    gNrotors += aNn.atomIndex
                    gNrotors += aC.atomIndex
                    gNrotors += aCa.atomIndex
                    gNrotors += aN.atomIndex
                }
            }
                        
            # phi
            if (aCa.group != "PRO") {
                if (((r <= nres) and (r > ares))
                    or ((r == ares) and (aname == "N"))) {
                    if (is_rotor_avail(aCa.atomIndex, aN.atomIndex)) {
                        gNrotors += aC.atomIndex
                        gNrotors += aCa.atomIndex
                        gNrotors += aN.atomIndex
                        gNrotors += aCp.atomIndex
                    }
                }
            }
        } # endfor
    }
    else {
        gCrotors = array()
        var cres = {atomIndex=gCcargoIdx}.resno
        var cname = {atomIndex=gCcargoIdx}.atomName
        var ares = get_resno_max(gChain)
        var aA = get_atom_rcn( ares, gChain, "C")
        if (gCanchorIdx >= 0) {
            aA = {atomIndex=gCanchorIdx}
        }
        var aname = aA.atomName
        for (var r = cres; r <= ares; r++) {
            var aCp = get_atom_rcn( r-1, gChain, "C")
            var aN = get_atom_rcn( r, gChain, "N")
            var aCa = get_atom_rcn( r, gChain, "CA")
            var aC = get_atom_rcn( r, gChain, "C")
            var aNn = get_atom_rcn( r+1, gChain, "N")
            if (aNn.size < 1) {
                aNn = aCp
            }
            
            # phi
            if (aCa.group != "PRO") {
                if (((r > cres) and (r < ares))
                    or ((r == cres) and (aname == "N"))
                    or ((r == ares) and (aname != "N"))) {
                    if (is_rotor_avail(aN.atomIndex, aCa.atomIndex)) {
                        gCrotors += aCp.atomIndex
                        gCrotors += aN.atomIndex
                        gCrotors += aCa.atomIndex
                        gCrotors += aC.atomIndex
                    }
                }
            }
            
            # psi
            if (((r >= cres) and (r < ares))
                or ((r == cres) and (aname != "C"))
                or ((r == ares) and (aname == "C"))) {
                if (is_rotor_avail(aCa.atomIndex, aC.atomIndex)) {
                    gCrotors += aN.atomIndex
                    gCrotors += aCa.atomIndex
                    gCrotors += aC.atomIndex
                    gCrotors += aNn.atomIndex
                }
            }
        } # endfor
    }
}

function collect_rotors() {
    collect_bb_rotors(false)
    collect_bb_rotors(true)
}

function tug_sc(pt) {

    # If destination atom defined
    if (gDestAtomIdx >= 0) {
        var v = {atomIndex=gDestAtomIdx}.xyz - {atomIndex=gSCidx}.xyz
        if (abs(angle({atomIndex=gDestAtomIdx}.xyz, {0 0 0}, pt)) < 90) {
            pt = -v/20.0
        }
        else {
            pt = v/20.0
        }
    }
    gSCpt += pt
    draw arrow {atomIndex=gSCidx} @gSCpt
}

function set_colors() {
    select (thisModel)
    color {selected} @gScheme
    color {atomIndex=g1pivotIdx} green
    color {atomIndex=g2pivotIdx} green
    color @gCargoSet @gAltScheme
    color {gCargoSet and oxygen} pink
    select {(atomIndex=gCcargoIdx) or (atomIndex=gNcargoIdx)
        or (atomIndex=gCanchorIdx) or (atomIndex=gNanchorIdx)}
    halo on
    select {atomIndex=gDestAtomIdx}
    star on
    select none
}

function clear_atom_idxs() {
    gCcargoIdx = -1
    gNcargoIdx = -1
    gCanchorIdx = -1
    gNanchorIdx = -1
    g1pivotIdx = -1
    g2pivotIdx = -1
    g1dynamicIdx = -1
    g2dynamicIdx = -1
    gDestAtomIdx = -1
    gSCidx = -1
}

function timed_out (s) {
    timeout ID"tug" OFF
    refresh
    if (prompt(format("%s - Undo?", s), "Yes|No", true) == "Yes") {
        gBusy = false
        restore state gState
        connect
        select gCargoSet
        set bondPicking true
        refresh
        for (var i = 1; i <= gFreeze.size; i+=2) {
            select {atomIndex=@{gFreeze[i]}} or {atomIndex=@{gFreeze[i+1]}}
            color bonds lightblue
        }
        background ECHO yellow
        echo @gEcho
        select all
        quit
    }
}

function record_drag() {
    var ls = format("select %s;", {selected})
    ls += format("gCanchorIdx = %d;", gCanchorIdx)
    ls += format("gCanchorNo = %d;", gCanchorNo)
    ls += format("gNanchorIdx = %d;", gNanchorIdx)
    ls += format("gNanchorNo = %d;", gNanchorNo)
    ls += format("gCcargoIdx = %d;", gCcargoIdx)
    ls += format("gNcargoIdx = %d;", gNcargoIdx)
    ls += format("gCcargoNo = %d;", gCcargoNo)
    ls += format("gNcargoNo = %d;", gNcargoNo)
    ls += format("gDestAtomIdx = %d;", gDestAtomIdx)
    ls += format("g1pivotIdx = %d;", g1pivotIdx)
    ls += format("g2pivotIdx = %d;", g2pivotIdx)
    ls += format("gOkCollide = %s;", gOkCollide)
    ls += format("gChain = \"%s\";", gChain)
    ls += format("gMinNo = %d;", gMinNo)
    ls += format("gMaxNo = %d;", gMaxNo)
    ls += format("gCargoSet = %s;", gCargoSet)
    ls += format("gSCidx = %d;", gSCidx)
    ls += format("gSCcircle = %d;", gSCcircle)
    ls += format("gSCpt = %s;", gSCpt)
    ls += "collect_rotors();"
    ls += "tug_drag_done_mb();"
    plico_record(ls)
}

# Pick call-back for freeze
function tug_pick_cb() {
    if (_pickInfo[3][6] == "bond") {
        var sel = {selected}
        var i = _pickInfo.find(":")
        var iChain = _pickInfo[i+1]
        i = _pickInfo.find("#")
        var a1no = 0 + _pickInfo[i+1][i+3]
        var j = _pickInfo[i+1][9999].find("#")
        var a2no = 0 + _pickInfo[i+j+1][i+j+3]
        var i1idx = {(atomno=a1no) and (chain=iChain)
            and thisModel}.atomIndex
        var i2idx = {(atomno=a2no) and (chain=iChain)
            and thisModel}.atomIndex
        if (({atomIndex=i1idx}.atomName == "CA")
            or ({atomIndex=i2idx}.atomName == "CA")) {
            if (({atomIndex=i1idx}.atomName != "CB")
                and ({atomIndex=i2idx}.atomName != "CB")) {
                if (i1idx > i2idx) {
                    var idx = 0 + i1idx
                    i1idx = 0 + i2idx
                    i2idx = 0 + idx
                }
                select {atomIndex=i1idx} or {atomIndex=i2idx}

                for (i = 1; i <= gFreeze.size; i += 2) {
                    if ((gFreeze[i] == i1idx) and (gFreeze[i+1] == i2idx)) {
                        if (i > 1) {
                            if (gFreeze.size = (i+1)) {
                                gFreeze = gFreeze[1][i-1]
                            }
                            else {
                                gFreeze = gFreeze[1][i-1] + gFreeze[i+2][0]
                            }
                        }
                        else {
                            gFreeze = gFreeze[i+2][0]
                        }
                        color bonds NONE
                        i = 0
                        break
                    }
                }
                if (i > gFreeze.size) {
                    gFreeze += i1idx
                    gFreeze += i2idx
                    color bonds lightblue
                }
            }
        }
        select {sel}
    }
}

# Bound to LEFT-UP by tug_enable_drag
function tug_drag_done_mb() {
    if (not gBusy) {
        if (gPlicoRecord != "") {
            record_drag()
        }

        # Move by rotation on rotor sets, smallest first
        gBusy = true
        background ECHO pink
        refresh

        # If side chain mode
        if (gSCidx >= 0) {
            drag_sc()
        }

        # Else
        else if (not gTow) {
            gOK = true
            timeout ID"tug" 20.0 "timed_out(\"Tug timed out\")"
            if ((gCrotors.size < gNrotors.size) or (gNanchorIdx < 0)) {
                if (gCrotors.size > 4) {
                    tug_track_c()
                }
                if (gOK and (gNrotors.size > 4)) {
                    tug_track_n()
                }
            }
            else {
                if (gNrotors.size > 4) {
                    tug_track_n()
                }
                if (gOK and (gCrotors.size > 4)) {
                    tug_track_c()
                }
            }
            timeout ID"tug" OFF

            # If anchor angles acute, fail
            if (gOK == true) {
                if (gCanchorIdx >= 0) {
                    var ic = get_cward_bb_idx(gCanchorIdx, gChain)
                    var in = get_nward_bb_idx(gCanchorIdx, gChain)
                    if ((ic >= 0) and
                        angle({atomIndex=ic}, {atomIndex=gCanchorIdx}, {atomIndex=in})
                        < 100.0) {
                        gOK = false
                    }
                }
                if (gNanchorIdx >= 0) {
                    var ic = get_cward_bb_idx(gNanchorIdx, gChain)
                    var in = get_nward_bb_idx(gNanchorIdx, gChain)
                    if ((in >= 0) and
                        angle({atomIndex=ic}, {atomIndex=gNanchorIdx}, {atomIndex=in})
                        < 100.0) {
                        gOK = false
                    }
                }
            }

            # If too far
            if (not gOK) {
                timed_out("TUG TOO FAR!")
            }

            # Else OK
            else {

                var idx = {
                    (atomno=@{{(chain=gChain) and thisModel}.atomno.min})
                    and (chain=gChain) and thisModel}.atomIndex


                var ihc = 0
                for (ihc = 0; ihc < 10; ihc++) {
                    select ((atomno >= gNanchorNo) and (atomno <= gCanchorNo)
                        and (chain = gChain) and thisModel)
                    handle_collisions( idx)
                    if (count_collisions(({})).size == 0) {
                        break
                    }
                }
                if (ihc == 10) {
                    timed_out("Unable to handle all collisions!")
                }
            }
        }
        select {gCargoSet}
        gBusy = false
        background ECHO yellow
        set bondPicking true
        refresh
    }
}

# Bound to ALT-SHIFT-LEFT-DRAG by tug_enable_drag
function tug_drag_2_mb() {
    tug_drag_mb(true)
}

# Bound to ALT-LEFT-DRAG by tug_enable_drag
function tug_drag_mb(alt) {
    if (not gBusy) {
        gBusy = true
        var dx = (40.0 * (_mouseX - gMouseX))/_width
        var dy = (40.0 * (_mouseY - gMouseY))/_height
        var q = quaternion()
        var ptd = {@dx @dy 0}
        var pt = (!q)%ptd
        var caxis = {0 0 0}
        if (distance(pt,  {0 0 0}) > 0.004) {
            # If sidechain mode
            if (gSCidx >= 0) {
                if ({atomIndex=gSCidx}.atomName == "O") {
                    if ({atomIndex=gSCidx}.group != "PRO") {
                        var dir = ((abs(dx) > abs(dy))
                            ? ((dx < 0) ? 10 : -10)
                            : ((dy < 0) ? 1 : -1))
                        counter_rotate(gSCidx, dir, not alt)
                    }
                }
                else {
                    gSCcheck = not alt
                    tug_sc(pt)
                }
            }

            # Else
            else {

                # If new drag
                if (gNewDrag) {
                    gNewDrag = false
                    save state gState
                }

                # If destination atom defined
                if (gDestAtomIdx >= 0) {
                    var v = {atomIndex=gDestAtomIdx}.xyz - {selected}.xyz
                    if (abs(angle({atomIndex=gDestAtomIdx}.xyz, {0 0 0}, pt)) < 90) {
                        pt = -v/20.0
                    }
                    else {
                        pt = v/20.0
                    }
                }

                # Move the cargo
                select {gCargoSet}

                # If pivots defined, rotate it
                if (g1pivotIdx >= 0) {

                    # If two pivots
                    if (g2pivotIdx >= 0) {
                        caxis = {atomIndex=g2pivotIdx}
                    }

                    # Else
                    else {
                        caxis = cross(pt, {0 0 0}) + {atomIndex=g1pivotIdx}.xyz
                    }

                    var dir = ((abs(dx) > abs(dy))
                        ? ((dx < 0) ? 2 : -2)
                        : ((dy < 0) ? 2 : -2))
                    rotate_selected_record(g1pivotIdx, caxis, dir)

                }

                # Else translate it
                else {
                    translate_selected_record(pt)
                }

                # If collisions
                var cNotSels = (within(kCtolerance, false, {selected})
                    and not {gMovingSet})
                if ((cNotSels) and (not alt)) {
                    gOk2 = true
                    for (var i = 1; i <= cNotSels.size;  i++) {

                        # If net collision vector same as move vector
                        var cSels = (within(kCtolerance, false, cNotSels[i]) and {selected})
                        for (var j = 1; j <= cSels.size;  j++) {
                            var v1 = cNotSels[i].xyz - cSels[j].xyz
                            if (abs(angle(v1, {0 0 0}, pt)) < 90) {

                                # If tow mode
                                if (gTow) {

                                    # Make a dynamic pivot
                                    if (g1pivotIdx < 0) {
                                        g1pivotIdx = cSels[j].atomIndex
                                        g1dynamicIdx = cNotSels[i].atomIndex
                                        color {atomIndex=g1pivotIdx} lightgreen
                                        set_distance_idx(cNotSels[i].atomIndex,
                                            cSels[j].atomIndex,
                                            kCtolerance + kDtolerance)
                                    }
                                    else if (g2pivotIdx < 0) {
                                        g2pivotIdx = cSels[j].atomIndex
                                        g2dynamicIdx = cNotSels[i].atomIndex
                                        color {atomIndex=g2pivotIdx} lightgreen
                                        set_distance_idx(cNotSels[i].atomIndex,
                                            cSels[j].atomIndex,
                                            kCtolerance + kDtolerance)
                                    }
                                    else {
                                        gOk2 = false
                                    }
                                }
                                else {

                                    # Try to resolve
                                    select @{cSels[j]}
                                    var idx = {(atomno=@{{(chain=gChain)
                                        and thisModel}.atomno.min})
                                        and (chain=gChain) and thisModel}.atomIndex
                                    handle_collisions( idx)
                                }
                            }
                        } # endfor
                        if (not gOk2) {
                            break
                        }
                    } # endfor

                    # If unable
                    if (not gOk2) {

                        # Back off
                        background ECHO pink
                        delay 1
                        if (g1pivotIdx >= 0) {
                            rotate_selected_record(g1pivotIdx, caxis, -a)
                        }
                        else {
                            translate_selected_record(-pt)
                        }
                        background ECHO yellow
                        set bondPicking true
                    }
                }
            }

            # If dynamic pivots
            if (g1dynamicIdx >= 0) {
                var v1 = {atomIndex=g1dynamicIdx}.xyz - {atomIndex=g1pivotIdx}.xyz
                if (abs(angle(v1, {0 0 0}, pt)) > 90) {
                    color {atomIndex=g1pivotIdx} @gAltScheme
                    g1pivotIdx = -1
                    g1dynamicIdx = -1

                }
            }
            if (g2dynamicIdx >= 0) {
                var v1 = {atomIndex=g2dynamicIdx}.xyz - {atomIndex=g2pivotIdx}.xyz
                if (abs(angle(v1, {0 0 0}, pt)) > 90) {
                    color {atomIndex=g2pivotIdx} @gAltScheme
                    g2pivotIdx = -1
                    g2dynamicIdx = -1
                }
            }

            gMouseX = _mouseX
            gMouseY = _mouseY
        }
        select {gCargoSet}
        gBusy = false
    }
}

# Bound to ALT-LEFT-DOWN by tug_enable_drag
function tug_mark_mb() {
    gMouseX = _mouseX
    gMouseY = _mouseY
    gNewDrag = true
}

# Called by tug_cargo_mb
function tug_enable_drag() {
    gEcho = "__________TUG__________|ALT-CLICK=mark block|SHIFT-CLICK=anchors" +
        "|ALT-CTRL-CLICK=pivots|ALT-SHIFT-CLICK=dest atom|ALT-DRAG=move" +
        "|ALT-SHIFT-DRAG=alt move|CLICK bond=freeze|ALT-DOUBLE-CLICK=undo" +
        "|SHIFT-DOUBLE-CLICK=exit"
    echo @gEcho

    # Allow atoms to be dragged
    bind "ALT-LEFT-DOWN" "tug_mark_mb";
    bind "ALT-LEFT-UP" "tug_drag_done_mb";
    bind "ALT-SHIFT-LEFT-DOWN" "tug_mark_mb";
    bind "ALT-LEFT-DRAG" "tug_drag_mb";
    bind "ALT-SHIFT-LEFT-DRAG" "tug_drag_2_mb";

    unbind "SHIFT-LEFT-CLICK"
    bind "SHIFT-LEFT-CLICK" "_pickAtom";
    bind "SHIFT-LEFT-CLICK" "+:tug_anchor_mb";

    bind "ALT-CTRL-LEFT-CLICK" "_pickAtom";
    bind "ALT-CTRL-LEFT-CLICK" "+:tug_pivot_mb";

    bind "ALT-SHIFT-LEFT-CLICK" "_pickAtom";
    bind "ALT-SHIFT-LEFT-CLICK" "+:tug_dest_atom_mb";
}

# Bound to SHIFT-LEFT-CLICK by tug_cargo_mb
function tug_anchor_mb() {
    if ({atomIndex=_atomPicked}.chain == gChain) {
        var aPidx = get_sc_bb_idx( _atomPicked, gChain)

        var pno = {atomIndex=aPidx}.atomno
        if (pno > {atomIndex=gCcargoIdx}.atomno) {
            select {atomIndex=gCanchorIdx}
            halo off
            if (gCanchorIdx == aPidx) {
                gCanchorIdx = -1
                gCanchorNo = gMaxNo + 1
            }
            else {
                gCanchorIdx = aPidx
                gCanchorNo = {atomIndex=gCanchorIdx}.atomno
                select {atomIndex=gCanchorIdx}
                halo on
            }
            collect_bb_rotors(false)
        }
        else if (pno < {atomIndex=gNcargoIdx}.atomno) {
            select {atomIndex=gNanchorIdx}
            halo off
            if (gNanchorIdx == aPidx) {
                gNanchorIdx = -1
                gNanchorNo = gMinNo - 1
            }
            else {
                gNanchorIdx = aPidx
                gNanchorNo = {atomIndex=gNanchorIdx}.atomno
                select {atomIndex=gNanchorIdx}
                halo on
            }
            collect_bb_rotors(true)
        }
        else {
            tow_cargo_mb()
        }

        # Get moving atoms set
        gMovingSet = {((atomno < gCanchorNo) and (atomno > gNanchorNo)
            and (chain=gChain) and thisModel)}
    }
    select {gCargoSet}
}

# Bound to ALT-SHIFT-LEFT-CLICK by tug_cargo_mb
function tug_dest_atom_mb() {
    var aOk = true
    if ({atomIndex=_atomPicked}.chain == gChain) {

        var pno = {atomIndex=_atomPicked}.atomno
        if ((pno <= {atomIndex=gCcargoIdx}.atomno)
            and (pno >= {atomIndex=gNcargoIdx}.atomno)) {
            aOk = false
        }
    }
    if (aOk) {
        select {atomIndex=gDestAtomIdx}
        star off
        if (gDestAtomIdx == _atomPicked) {
            gDestAtomIdx = -1
        }
        else {
            gDestAtomIdx = _atomPicked
            select {atomIndex=gDestAtomIdx}
            star on
        }
        select {gCargoSet}
    }
}

# Bound to CTRL-LEFT-CLICK by tug_cargo_mb
function tug_pivot_mb() {
    if (g1pivotIdx == _atomPicked) {
        color {atomIndex=g1pivotIdx} @gScheme
        if (g2pivotIdx >= 0) {
            g1pivotIdx = g2pivotIdx
            g2pivotIdx = -1
            g2dynamicIdx = -1
        }
        else {
            g1pivotIdx = -1
            g1dynamicIdx = -1
        }
    }
    else if (g2pivotIdx == _atomPicked) {
        color {atomIndex=g2pivotIdx} @gScheme
        g2pivotIdx = -1
        g2dynamicIdx = -1
    }
    else if (g1pivotIdx >= 0) {
        if (g2pivotIdx >= 0) {
            color {atomIndex=g2pivotIdx} @gScheme
        }

        g2pivotIdx = _atomPicked
        g2dynamicIdx = -1
        color {atomIndex=g2pivotIdx} green
    }
    else {
        g1pivotIdx = _atomPicked
        g1dynamicIdx = -1
        color {atomIndex=g1pivotIdx} green
    }
    select {gCargoSet}
}

# Bound to SHIFT-LEFT-CLICK by plico_tug
function tow_cargo_mb() {
    gTow = true
    gChain = {atomIndex=_atomPicked}.chain
    gMinNo = {(chain=gChain) and thisModel}.atomno.min
    gMaxNo = {(chain=gChain) and thisModel}.atomno.max
    gCcargoIdx = -1
    gNcargoIdx = -1
    gCanchorIdx = -1
    gCanchorNo = gMaxNo + 1
    gNanchorIdx = -1
    gNanchorNo = gMinNo - 1

    # Highlight cargo cluster
    select {(chain=gChain) and thisModel}
    gCargoSet = {selected}
    gMovingSet = {selected}
    set_colors()

    # Enable dragging
    tug_enable_drag()
    select {gCargoSet}
    halo off
    var es = gEcho.replace("anchors","mark chain")
    echo @es
    unbind "SHIFT-LEFT-CLICK"
    bind "SHIFT-LEFT-CLICK" "_pickAtom";
    bind "SHIFT-LEFT-CLICK" "+:tow_cargo_mb";
}

# Bound to ALT-LEFT-CLICK by plico_tug or called by plicotoab.toabCargoMB
function tug_cargo_mb() {

    # If O or movable side chain atom picked
    if ((({atomIndex=_atomPicked}.atomName = "O")
        and ({atomIndex=_atomPicked}.group != "PRO"))
        or (is_moveable_sc( _atomPicked))) {
        if (gSCidx >= 0) {
            draw gSCcircle DELETE
        }
        if (gSCidx == _atomPicked) {
            gSCidx = -1
        }
        else {
            gSCidx = _atomPicked
            gSCpt = {atomIndex=gSCidx}.xyz
            draw gSCcircle CIRCLE {atomIndex=gSCidx} MESH NOFILL
        }
    }
    else {
        if ({atomIndex=_atomPicked}.chain != gChain) {
            if (gTow) {
                select {(chain=gChain) and thisModel}
                color {selected} @gScheme
            }
            gChain = {atomIndex=_atomPicked}.chain
            select ({atomIndex=gCcargoIdx} or {atomIndex=gNcargoIdx}
                or {atomIndex=gCanchorIdx} or {atomIndex=gNanchorIdx})
            halo off
            gCcargoIdx = -1
            gNcargoIdx = -1
            gCanchorIdx = -1
            gNanchorIdx = -1
        }
        gTow = false
        gMinNo = {(chain=gChain) and thisModel}.atomno.min
        gMaxNo = {(chain=gChain) and thisModel}.atomno.max
        if (gNanchorIdx < 0) {
            gNanchorNo = gMinNo - 1
        }
        if (gCanchorIdx < 0) {
            gCanchorNo = gMaxNo + 1
        }
        var aPidx = get_sc_bb_idx( _atomPicked, gChain)

        gSCidx = -1
        draw gSCcircle DELETE

        # If existing cWard cargo picked
        if (gCcargoIdx == aPidx) {

            # Clear the highlight
            select {atomIndex=gCcargoIdx}
            halo off

            # If nWard cargo exists, mark it as the cWard cargo
            if (gNcargoIdx != gCcargoIdx) {
                gCcargoIdx = get_cp_idx(gNcargoIdx)
                gCcargoNo = {atomIndex=gCcargoIdx}.atomno
            }
            else {
                gCcargoIdx = -1
                gNcargoIdx = -1
            }
        }
        else if (gNcargoIdx == aPidx) {
            select {atomIndex=gNcargoIdx}
            halo off
            gNcargoIdx = get_nm_idx(gCcargoIdx)
            gNcargoNo = {atomIndex=gNcargoIdx}.atomno
        }
        else if (gCcargoIdx >= 0) {

            var no = {atomIndex=aPidx}.atomno

            # If pick is nWard of it
            if (no < {atomIndex=gCcargoIdx}.atomno) {

                # If exists, clear its highlight
                if (gNcargoIdx != gCcargoIdx) {
                    select {atomIndex=gNcargoIdx}
                    halo off
                }

                # Set new nWard cargo and highlight it
                gNcargoIdx = get_nm_idx(aPidx)
                gNcargoNo = {atomIndex=gNcargoIdx}.atomno
            }

            # Else cWard
            else {

                # Clear its old highlight
                select {atomIndex=gCcargoIdx}
                if (gNcargoIdx != gCcargoIdx) {
                    halo off
                }

                # Set new cWard cargo and highlight
                gCcargoIdx = get_cp_idx(aPidx)
                gCcargoNo = {atomIndex=gCcargoIdx}.atomno
            }
        }

        # Else no cWard cargo
        else {

            # Set new cWard cargo and highlight
            gCcargoIdx = get_cp_idx(aPidx)
            gCcargoNo = {atomIndex=gCcargoIdx}.atomno
            gNcargoIdx = get_nm_idx(gCcargoIdx)
            gNcargoNo = {atomIndex=gNcargoIdx}.atomno
        }

        # If any anchor now inside cargo cluster, kill it
        if ({atomIndex=gCanchorIdx}.atomno <= {atomIndex=gCcargoIdx}.atomno) {
            gCanchorIdx = -1
            gCanchorNo = gMaxNo + 1
        }
        if ({atomIndex=gNanchorIdx}.atomno >= {atomIndex=gNcargoIdx}.atomno) {
            gNanchorIdx = -1
            gNanchorNo = gMinNo - 1
        }

        # Highlight cargo cluster
        select_nward_idx(gCcargoIdx, gNcargoIdx)
        gCargoSet = {selected}
        set_colors()

        # Collect the rotor sets
        collect_rotors()

        # Get moving atoms set
        gMovingSet = {((atomno < gCanchorNo) and (atomno > gNanchorNo)
            and (chain=gChain) and thisModel)}
    }

    # Enable dragging
    if (gToab) {
        to_ab_enable_drag()
    }
    else {
        tug_enable_drag()
    }

    select {gCargoSet}
}

function tug_undo_mb() {
    if (prompt("Undo", "Yes|No", true) == "Yes") {
        restore state gState
    }
}

# Top level of Tug
function plico_tug() {

    # Load common functions if not already
    if (kCommon < 7) {
        script $SCRIPT_PATH$plicoCommon.spt
        if (kCommon < 7) {
            prompt ("A newer version of plicoCommon.SPT is required")
            quit
        }
    }

    gPlico = "TUG"
    plico_prelim(true, true)
    
    gBondPicking = bondPicking
    set bondPicking true
    set PickCallback "jmolscript:tug_pick_cb"

    gEcho = ("_________TUG_________|ALT-CLICK=mark block|SHIFT-CLICK=mark chain" +
        "|CLICK bond=freeze|ALT-DOUBLE-CLICK=undo|SHIFT-DOUBLE-CLICK=exit")
    echo @gEcho
    gCrotors = array()
    gNrotors = array()
    clear_atom_idxs()
    gToab = false

    bind "ALT-LEFT-CLICK" "_pickAtom";
    bind "ALT-LEFT-CLICK" "+:tug_cargo_mb";
    bind "SHIFT-LEFT-CLICK" "_pickAtom";
    bind "SHIFT-LEFT-CLICK" "+:tow_cargo_mb";
    bind "ALT-LEFT-DOUBLE" "tug_undo_mb";
    bind "SHIFT-DOUBLE" "tug_exit(true)";
    bind "LEFT-CLICK" "+:plico_menu_toggle";
}

# Bound to DOUBLE by plico_tug
function tug_exit() {
    if (plico_exit()) {
        set bondPicking gBondPicking
        set PickCallback NONE
        select all
        color bonds none
        
        reset kTug
        reset gCanchorIdx  
        reset gCanchorNo   
        reset gPlico       
        reset gNanchorIdx  
        reset gNanchorNo   
        reset gCcargoIdx   
        reset gNcargoIdx   
        reset gCcargoNo    
        reset gNcargoNo    
        reset gDestAtomIdx 
        reset g1pivotIdx   
        reset g2pivotIdx   
        reset gSelSaves    
        reset gCrotors     
        reset gNrotors     
        reset gOkCollide   
        reset gChain       
        reset gMinNo       
        reset gMaxNo       
        reset gCargoSet    
        reset gMovingSet   
        reset gSCidx       
        reset gSCcircle    
        reset gSCpt        
        reset gTargetPt    
        reset gNewDrag     
        reset gTow         
        reset g1dynamicIdx 
        reset g2dynamicIdx 
        reset gSCcheck     
        reset gBondPicking 
        reset gFreeze      
        reset gToab        
        reset function "get_cp_idx"
        reset function "get_cm_no"
        reset function "get_nm_idx"
        reset function "get_np_no"
        reset function "get_cb_idx"
        reset function "get_o_idx"
        reset function "get_nward_bb_idx"
        reset function "get_cward_bb_idx"
        reset function "get_sc_set"
        reset function "get_sc_bb_idx"
        reset function "is_bb_idx"
        reset function "is_sc_idx"
        reset function "select_add_sc"
        reset function "handle_collisions"
        reset function "tug_track_idx"
        reset function "do_counter_rotate"
        reset function "counter_rotate"
        reset function "counter_rotate_2"
        reset function "repair_proline"
        reset function "repair_sc"
        reset function "tug_track_c"
        reset function "tug_track_n"
        reset function "translate_selected_record"
        reset function "rotate_selected_record"
        reset function "collect_sc_rotors"
        reset function "drag_sc"
        reset function "fix_sc_collision_2"
        reset function "is_moveable_sc"
        reset function "is_rotor_avail"
        reset function "xcollect_bb_rotors"
        reset function "collect_bb_rotors"
        reset function "collect_rotors"
        reset function "tug_sc"
        reset function "set_colors"
        reset function "clear_atom_idxs"
        reset function "timed_out"
        reset function "record_drag"
        reset function "tug_pick_cb"
        reset function "tug_drag_done_mb"
        reset function "tug_drag_2_mb"
        reset function "tug_drag_mb"
        reset function "tug_mark_mb"
        reset function "tug_anchor_mb"
        reset function "tug_enable_drag"
        reset function "tug_dest_atom_mb"
        reset function "tug_pivot_mb"
        reset function "tow_cargo_mb"
        reset function "tug_cargo_mb"
        reset function "tug_undo_mb"
        reset function "plico_tug"


    }
}

# End of TUG.SPT

Contributors

Remig