Code
--[[
* Loop Turn-Twist
* Original Author: Brow42 Mar. 12 2012
* Version 0.1 beta May 30, 2012 (valid to July 15 2012)
* This script will turn and/or twist a region of the protein.
* Unlike the tweak tool, it work on loops, not just sheets,
* and it can make a twist, not just straighten. A good use
* is to continue the turn/twist around U-turns at the end of a sheet.
*
* The script has a built in undo function, so you can back
* up if you turn/twist too far, or in the wrong direction.
*
* Unfortunately, it doesn't seem strong enough to actually do
* turn/twist. This recipe is experimental.
* Version 1.0 July 18 2012 Brow42
* removed beta notice
--]]
title = 'Loop Turner-Twister 1.0'
rangestr = "" -- initial range
--beta_date = os.time({year=2012, month=7, day=15})
-- ================================== Begin New Dialog Library Functions
--[[
Throws up a dialog containing just text, provided as a string or table
of strings in the first argument. Title and buttons are optional. Return
value is 1 if the first button is clicked and 0 if the second button is clicked
or the window is closed.
--]]
function dialog.MessageBox(msg,title,buttontext1, buttontext0)
title = title or ''
local d = dialog.CreateDialog(title)
if type(msg) == 'string' then d['1'] = dialog.AddLabel(msg)
else for i = 1,#msg do
d[tostring(i)] = dialog.AddLabel(msg[i])
end
end
buttontext1 = buttontext1 or 'Ok'
d.button = dialog.AddButton(buttontext1,1)
if buttontext0 then d.button0 = dialog.AddButton(buttontext0,0) end
return dialog.Show(d)
end
-- ================================== End New Dialog Library Functions
-- ========================== Begin Small AA Atom Database ================
fsl = fsl or {} -- make sure fsl namespace exists
fsl.atom = {}
-- Take just these 4 things if all you need is to know if the sidechain starts at 5 or 6 (tail terminal = true)
fsl.atom.atomcount = { -- shorter table just for identifying terminals
a=10, c=11, d=12, e=15, f=20, g=7, h=17, i=19, k=22, l=19,
m=17, n=14, p=15, q=17, r=24, s=11, t=14, v=16, w=24, y=21
}
-- Pass in a segment number of a cysteine
function fsl.atom._IsDisulfideBonded(iSeg)
local s = current.GetSegmentEnergySubscore(iSeg,'disulfides')
return tostring(s) ~= '-0'
end
function fsl.atom._IsTerminalTest(aa,count,disulfide)
local dbvalue = fsl.atom.atomcount[aa]
if dbvalue == nil then error('Bad argument to an fsl.atom function (not an amino acid code)') end
local diff = count - dbvalue
if disulfide then diff = diff + 1 end -- because count is one less because a H was removed
if diff == 0 then return false, false, disulfide
elseif diff == 1 then return false, true, disulfide
elseif diff == 2 then return true, false, disulfide
elseif diff == 3 then return true, true, disulfide
end
error('Strange atom count, report this!')
end
-- Determine if segment is start or end of polypeptide by looking for excess atoms
function fsl.atom.IsTerminal(iSeg)
if structure.GetSecondaryStructure(iSeg) == 'M' then return false,false,false end
local aa = structure.GetAminoAcid(iSeg)
local count = structure.GetAtomCount(iSeg)
return fsl.atom._IsTerminalTest(aa,count,fsl.atom._IsDisulfideBonded(iSeg))
end
-- ========================== End Small AA Atom Database ================
-- ================================== Begin Undo
-- Undo functionality
undo = {}
undo._NUndo = 25 -- 25 undo slots in the gui so max 25
undo._iSlot = 1 -- the next slot to be saved into 1 to NUndo
undo._NSaved = 0 -- 0 to NUndo
-- Get how many saved solutions there are
function undo.GetCount() return undo._NSaved end
-- Pushes current state onto circular undo buffer, returns buffer size
function undo.Save()
save.Quicksave(101 - undo._iSlot)
undo._iSlot = 1 + undo._iSlot % undo._NUndo
undo._NSaved = math.min(undo._NSaved + 1, undo._NUndo)
return undo._NSaved
end
-- pops the top save from undo buffer, returns false if empty
function undo.Undo()
if undo._NSaved < 1 then error('No more saves in undo buffer') end
undo._NSaved = undo._NSaved - 1
undo._iSlot = 1 + (undo._iSlot +undo._NUndo - 2) % undo._NUndo
save.Quickload(101-undo._iSlot)
return undo._NSaved > 0
end
-- Pull the oldest save out (FIFO)
function undo.PopTail()
if undo._NSaved < 1 then error('No more saves in undo buffer') end
local i = 1 + (undo._iSlot - 1 - undo._NSaved + undo._NUndo) % undo._NUndo
save.Quickload(101-i)
undo._NSaved = undo._NSaved - 1
return undo._NSaved > 0
end
-- ================================== End Undo
-- ================================== Begin Dialogs
function About()
local msg = {
'This rotates and twists loops and sheets.' ,
'RIGHT means: pretend to wrap your right hand' ,
'around a loop with the thumb pointing to the' ,
'higher-number segement. Your fingers curl in' ,
'RIGHT direction (+1). LEFT is the opposite (-1).' ,
'Twist +1 mean extra RIGHT (or less LEFT) at' ,
'end than at the start. -1 means the opposite.',
'Twist = Rotate means first segment wont\'t turn.',
'Twist = - Rotate means last won\'t turn',
'',
'Repetitions - 1 or more turn/twist per click',
'Undo - back up 1 turn/twist',
'Atom - distance up the sidechain to band'
}
dialog.MessageBox(msg,'About '..title)
end
function MainDialog()
local d = dialog.CreateDialog(title)
if beta_date then
d.beta = dialog.AddLabel(string.format(
' * This is a BETA recipe that expires %s.',os.date('%b. %d %Y',beta_date)))
end
d.label1 = dialog.AddLabel('Rotate loops with optional extra twist.')
d.label2 = dialog.AddLabel('Imagine a rolled up towel in a U-shape...')
d.label3 = dialog.AddLabel('1) Band to save shape 2) select in Selection')
d.label4 = dialog.AddLabel('or in box 3) Set settings 4) Click Turn 5) Repeat')
d.rotation = dialog.AddSlider('Rotate (band len):',0,-2,2,2)
d.twist = dialog.AddSlider('Twist :',0,-2,2,2)
d.str = dialog.AddSlider('Strength:',1,0.1,10,1)
d.repeating = dialog.AddSlider('Repetitions:',1,1,10,0)
d.nwiggle = dialog.AddSlider('# Wiggle per turn:',3,1,10,0)
d.atom = dialog.AddSlider('Sidechain atom:',1,1,10,0)
d.skip = dialog.AddSlider('Band every N segs:',1,1,5,0)
d.start = dialog.AddButton('Turn Some',1)
d.cancel = dialog.AddButton('Exit',0)
d.about = dialog.AddButton('Help',2)
d.undo = dialog.AddButton('Undo',3)
d.range = dialog.AddTextbox('Range',rangestr)
return d
end
-- returns a table of 2 numbers or nil
function ParseRangeString(str)
local r = {}
for x in str:gfind('%d+') do r[#r+1]=tonumber(x) end
if #r == 1 then return {r[1], r[1]} end
if #r ~= 2 then return end
if r[1] > r[2] then r[1],r[2] = r[2],r[1] end
if r[1] < 1 or r[2] > structure.GetCount() then return end
return r
end
-- ================================== End Dialogs
-- ================================== Begin Work Functions
-- Make a right-angle band
-- If len is negative, reverse it
function Band(iSeg, len, atom, str)
local goal = 0.01
if structure.GetAminoAcid(iSeg) == 'g' then return end
local n = structure.GetCount()
local s2,s3
if iSeg == 1 then s2,s3 = 2,3
elseif iSeg == n then s2,s3,len = n-1,n-2,-len
else s2,s3 = iSeg+1, iSeg-1
end
local phi, theta = 0, math.pi/2
if len < 0 then phi,theta,len = phi + math.pi, theta, -len
elseif len == 0 then len = goal
end
local iBand = band.Add(iSeg,s2,s3,len,phi,theta,atom)
if iBand > 0 then
band.SetStrength(iBand,str)
band.SetGoalLength(iBand,goal)
end
end
-- Band up segments in range rotating 'rot',
-- adding a twist 'twist' on top of that,
-- with band str 'str' on sidechain atom 'atom'
-- and also a pinning band on the backbone C-alpha
function DoIt(range,rot,twist,str,atom,skip)
local s
local n = range[2] - range[1] + 1
local m = (n-1)/2
if n == 1 then twist,m = 0, 1 end -- no twist if just 1 seg
for i = 0,n-1,skip do
j = i + range[1]
s = rot + twist * (i-m)/m -- combined rotation and twist
-- s = s * (i % 2 - 0.5) * 2 -- goofiness
-- print (i,s)
if structure.GetSecondaryStructure(j) ~= 'M' then -- no ligands
local sc = 4 + atom
if sc <= fsl.atom.atomcount[structure.GetAminoAcid(j)] then
local first,last,disulfide = fsl.atom.IsTerminal(j)
Band(j,s, sc + (first and 1 or 0),str)
end
end
Band(j,0,0,str) -- pinning band
end
end
-- ================================== End Work Functions
-- ================================== Begin Main
if beta_date and beta_date < os.time() then
dialog.MessageBox({'This beta software has expired.','Please download a new copy today!'},'Recipe Update')
end
-- use initial selection if present
if rangestr == "" then
local list = {}
for i = 1,structure.GetCount() do
if selection.IsSelected(i) then list[#list+1] = i end
end
if #list > 0 then rangestr = string.format("%d %d",list[1],list[#list]) end
end
d = MainDialog()
while true do
d.undo.label = string.format("Undo (%d)",undo.GetCount())
local rc = dialog.Show(d)
if rc == 0 then break
elseif rc == 2 then About()
elseif rc == 3 then
if undo.GetCount() > 0 then undo.Undo()
else dialog.MessageBox('No turn steps left to undo!',"Can't Undo")
end
else
r = ParseRangeString(d.range.value)
if r then
for i = 1, d.repeating.value do
band.DeleteAll()
undo.Save()
DoIt(r,d.rotation.value,d.twist.value,d.str.value,d.atom.value,d.skip.value)
structure.WiggleAll(d.nwiggle.value)
band.DeleteAll()
end
else
dialog.MessageBox('Enter 1 or 2 numbers specifying the region to turn/twist','Range Error')
end
end
end