Code
--[[
====================================
* Zero Length Bands
* Original Author: Brow42
* Version 1.2 Jan. 13 2013 Brow42
* Added options to only band certain things
* Moved some options to a second menu, removed reset menu button
* Ranges are now expressed as num-num
* Random bands actually have a non-zero length finally.
* Version 1.0.4 Jun. 10 2012 Brow42
* Added option to band glycine even if backbone is not selected
* Version 1.0.3 Apr. 24 2012 Brow42
* Fixed silly bugs in 1.0.2...always band sidechains, band wrong sc atom
* Version 1.0.2 Apr. 23 2012 Brow42
* Added slider to not band every single one
* Now bands the O-terminus (last seg) sidechain atom correctly
* Version 1.0.1 Jan 7 2012 Brow42
* Restricted U-Turn detection to loops only
* Made band length and goal different when randomized so it actually pulls
* Version 1.0 Jan. 5 2012
* Applies zero-length bands to freeze the overall position or to get started
* in a QTTN or guide match.
* Can randomized to unfreeze a protein
--]]
-- =================================== Begin Options
option={}
option.backbone = true-- Lock down the backbone
option.sidechains = false-- Best option for QTTN
-- option.extrema = false -- Lock distant points -- not implemented
option.uturn = false -- Lock what seems like U-Turns
option.bandstr = 3.0
option.random = 0.0
option.uradius = 6.5
option.sidechainatom = 1
option.range_text = ''
option.ranges = {} -- table of ranges
option.skip_offset = 0
option.skip = 0
option.pin_glycine = false
option.do_loop = false
option.do_sheet = false
option.do_helix = false
option.do_frozen = false
option.do_notfrozen = false
option.do_selected = false
-- Globals
version = '2.0.1'
title='Zero Length Bands v'..version
max_skip = 5 -- for the skip sliders
nseg = structure.GetCount()
-- ================================== End Defaults
-- ================================== 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 d
end
function dialog.ShowMessageBox(msg,title,buttontext1,buttontext0)
return dialog.Show(dialog.MessageBox(msg,title,buttontext1,buttontext0))
end
--[[
Add items to the dialog from a table in the order given. The each table entry
is the dialog control function, the control label, the data key name, and the control options.
If this function fails, it gives an error saying which table item it failed on.
d = a created dialog, fields = table of dialog controls as described, object = data table
--]]
function dialog.AddFromTable(d,fields,object)
local func, label, id, value -- renamed field parameters for clarity
-- function wrappers to catch errors, explicitly named for clarity
function _AddLabel(args) d[id] = dialog.AddLabel(label) end
function _AddCheckbox(args) d[id] = dialog.AddCheckbox(label,value) end
function _AddSlider(args) d[id] = dialog.AddSlider(label,value,args[4],args[5],args[6]) end
function _AddTextbox(args) d[id] = dialog.AddTextbox(label,value) end
for i = 1, #fields do
local rc = true
local err
rc,err = pcall( function() func,label,id = fields[i][1],fields[i][2],fields[i][3] end)
value = object[id]
if func == dialog.AddLabel then id='_AL'..tostring(i) rc,err = pcall(_AddLabel,fields[i])
else
-- this is a crasher for AddTextbox but not an error for AddLabel!
if value == nil then error('Missing data field for field #'..tostring(i)..'\n(Did you forget to pass a string?)') end
if func == dialog.AddCheckbox then rc,err = pcall(_AddCheckbox,fields[i])
elseif func == dialog.AddSlider then rc,err = pcall(_AddSlider,fields[i])
elseif func == dialog.AddTextbox then rc,err = pcall(_AddTextbox,fields[i])
else error('Unknown control in dialog field #'..tostring(i)) end
end -- if AddLabel
if rc == false then
error('Error setting dialog field #'..tostring(i)..(err and ('\n'..err) or ''))
end
end
end
--[[
This just copies the data from the dialog to the data table.
--]]
function dialog.ReadFields(d,fields,object)
for i = 1,#fields do
if fields[i][1]~= dialog.AddLabel then
if d[fields[i][3]] == nil then error('Field '..tostring(i)..' is missing from dialog',2) end
if object[fields[i][3]] == nil then error('Field '..tostring(i)..' is missing from object',2) end
object[fields[i][3]] = d[fields[i][3]].value
end
end
end
-- ================================== End New Dialog Library Functions
-- ================================== Begin Zero Length Bands
-- ==================== Begin Dialog Code
-- This function defines our dialog and calls the functions to set and read values
-- First arg is the current option state and optional second arg is a remembered
-- default.
function ShowMenu()
-- Short names for the dialog controls
-- this is not an invitation to override, they are not used as functions
local label = dialog.AddLabel -- no data member for this
local box = dialog.AddCheckbox
local slider = dialog.AddSlider -- 4 control options for this
local text = dialog.AddTextbox
local msg
local dialogs = {}
-- Lay out the dialog here
-- { { dialog control, textlabel, data member name, control options }, ... }
-- all data member names must be unique
local fields1 ={
{label,'Pick one or more options:'},
{box, 'Backbone', 'backbone'},
{box,'Sidechains','sidechains'},
{label,'Check what you want banded, or leave all unchecked.'},
{box, 'Band Loops', 'do_loop'},
{box, 'Band Sheets', 'do_sheet'},
{box, 'Band Helices', 'do_helix'},
{text,'Segments:', 'range_text'},
{slider,'Band Strength:','bandstr',0.1,10.0,1},
{slider,'Seg. Skip:','skip',0,max_skip,0},
{slider,'Skip offset:','skip_offset',0,max_skip,0},
{label,'Uncommon options moved to "More"'}
}
dialogs.main = dialog.CreateDialog(title)
-- Generate the dialog in order of the table
-- You can still do this by hand for a custom layout if you wish
dialog.AddFromTable(dialogs.main,fields1,option) -- last arg = table to read from
-- Add the button now
dialogs.main.ok = dialog.AddButton('Start!',1) -- 1 = okay
dialogs.main.cancel = dialog.AddButton('Cancel',0) -- 0 = quit
dialogs.main.help = dialog.AddButton('Help',-1) -- < 0 = show options again, optional button
dialogs.main.more = dialog.AddButton('More',-2) -- extra options
local fields2 = {
{slider,' SC Atom:','sidechainatom',1,30,0},
{box,'Pin glycines even if no backbone bands','pin_glycine'},
{box,'U-Turns (set SS to exclude helices)','uturn'},
{slider,'U-Turn Detection:','uradius',3.5,10.0,1},
{slider,'Randomize:','random',0.0,1.5,2},
}
if _nfrozen > 0 then
fields2[#fields2+1] = {box, 'Frozen only', 'do_frozen'}
fields2[#fields2+1] = {box, 'Not-frozen only', 'do_notfrozen'}
else
fields2[#fields2+1] = {label, 'No frozen or locked segments'}
end
if _nselected > 0 then
fields2[#fields2+1] = {box, 'Selected only', 'do_selected'}
else
fields2[#fields2+1] = {label, 'No selected segments'}
end
dialogs.more = dialog.CreateDialog(title)
dialog.AddFromTable(dialogs.more,fields2,option)
dialogs.more.ok = dialog.AddButton('Back',1)
msg = {
' This recipe is meant to assist with QTTN or other',
'guided puzzles. You can band all the backbone',
'and/or all the sidechains to their current position.',
'You still have to drag the other ends where you',
'want them.',
'',
' Range one or more <num> or <num-num> where',
'num is a segment. Change skip to band every-Nth',
'for, fewer segments and offset to change which are',
'skipped. Optionally select which structures are',
'banded, (default is all structures.)'
}
dialogs.help1 = dialog.MessageBox(msg,title..' Help Page 1','Main')
dialogs.help1.next = dialog.AddButton('Next',2)
msg = {
' Alternatively you can use the U-Turn detector',
'which bands U-turns...this helps keep the protein',
'exploding or wandering off without restricting the',
'wiggle. Set helix structure so they are not detected.',
'',
' The atom slider picks the side chain atom, default',
'is the 1st carbon (glycine doesn\'t have one). The',
'random slider gives the bands a non-zero length.',
'The U-Turn slider makes more-twisted turns detected.',
'',
' If you have frozen, locked, or selected segements,',
'you can optionally band only locked/frozen, free,',
'or selected segments.'
}
dialogs.help2 = dialog.MessageBox(msg,title..' Help Page 2','Main')
dialogs.help2.next = dialog.AddButton('Previous',3)
repeat
local button = dialog.Show(dialogs.main)
if button == 0 then return 0 -- 0 = cancel
elseif button == 1 then
dialog.ReadFields(dialogs.main, fields1, option)
option.ranges = RangeStringToTable(option.range_text)
if ValidateInput1() then break end -- validate input and print errors
elseif button == -1 then -- help menu 2 pages
local d = dialogs.help1
repeat
button = dialog.Show(d)
if button == 2 then d = dialogs.help2 -- next
elseif button == 3 then d = dialogs.help1 -- previous
elseif button == 0 then return 0 end
until button == 1
elseif button == -2 then -- more menu
repeat
local button = dialog.Show(dialogs.more)
if button == 0 then return 0 -- 0 = cancel
elseif button == 1 then
dialog.ReadFields(dialogs.more, fields2, option)
if ValidateInput2() then break end -- validate input and print errors
end
until false
end
until false
return 1
end
-- Returns true if all the 1st page options are valid
function ValidateInput1()
if option.skip_offset > option.skip then
dialog.ShowMessageBox('The offset setting must be less than/equal # skipped.','Input Error','Okay')
return false
end
return option.ranges ~= nil -- parse errors were already reported so no message
end
-- Returns true if all the 2nd page options are valid
function ValidateInput2()
if option.do_notfrozen and not option.do_frozen and _nfrozen == nseg then
dialog.ShowMessageBox('Everything is frozen/locked, none will be banded.','Input Error','Okay')
return false
end
return true
end
function RangeStringToTable(str)
local tab = {}
local entry
local state = 1
local N = structure.GetCount()
for val in str:gmatch('-? *%d+') do
val = tonumber(val)
if (val < 0 and state == 1) or val == 0 then
dialog.ShowMessageBox("Only positive values >1 are allowed in the range","Input Error","Okay")
return nil
end
if math.abs(val) > N then
dialog.ShowMessageBox("Segment number bigger than number of segments","Input Error","Okay")
return nil
end
if val > 0 then
tab[#tab+1] = entry
entry = {val}
state = 2
else
entry[#entry+1] = -val
tab[#tab+1] = entry
entry = nil
state = 1
end
end
if entry then tab[#tab+1] = entry end
return tab
end
-- Recursive table printer
function PrintTable(tab,indent)
indent = indent or ''
for i,v in pairs(tab) do
if type(v) == 'table' then do print(indent,i) PrintTable(v,indent..' ') end
else print(indent,i,v) end
end
end
function IsUTurn(i)
if i >= 1 and i+3 <= nseg and structure.GetDistance(i,i+3) < option.uradius
and structure.GetSecondaryStructure(i+1) == 'L' then
return true
end
return false
end
-- True if nothing is specified
function AllSegments()
return AllStructures() and allFrozen()
and not (option.do_selected or option.uturn) and #_seglist == 0
end
-- True if no structures or all structures specified
function AllStructures()
return not (option.do_loop or option.do_sheet or option.do_helix) or
(option.do_loop and option.do_sheet and option.do_helix)
end
-- True of neither or both are checked
function AllFrozen()
return (option.do_frozen and option.do_notfrozen) or not (option.do_frozen or option.do_notfrozen)
end
-- Returns true if a segment satisfies all conditions (except range)
function DoSegment(i)
local s = structure.GetSecondaryStructure(i)
local f = freeze.IsFrozen(i) or structure.IsLocked(i)
local retval = true
retval = retval and s ~= 'M'
retval = retval and (_allstructures or ( option.do_loop and s == 'L') or (option.do_sheet and s == 'E') or (option.do_helix and s == 'H'))
retval = retval and (_allfrozen or (option.do_frozen and f) or (option.do_notfrozen and not f))
retval = retval and (not option.do_selected or selection.IsSelected(i))
retval = retval and (not option.uturn or IsUTurn(i))
retval = retval and ( (i - option.skip_offset -1) % (1+option.skip) == 0)
return retval
end
fsl = fsl or {}
fsl.atom = fsl.atom or {}
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
}
function fsl.atom._IsDisulfideBonded(iSeg)
local s = current.GetSegmentEnergySubscore(iSeg,'disulfides')
return tostring(s) ~= '-0'
end
function fsl.atom._IsTerminalTest(aa,count,disulfide)
local diff = count - fsl.atom.atomcount[aa]
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
-- Return random phi,theta that is uniformly distributed over the sphere
-- phi between 0 and Pi, theta between 0 and 2 Pi
-- You can pass these straight into band.Add
function RandomSphere()
local u = 2* math.random() - 1
return math.acos(u), math.random()*2*math.pi
end
-- Make a band in a random direction
-- default length is 0.001
-- default strength is 1.0
-- default atom is 0
function RandomBand(iSeg,atom,goal,weight)
local nSeg = structure.GetCount()
local seg2, seg3
local phi =0
local theta = 0
local minlen = 0.01
atom = atom or 0
goal = goal or minlen
weight = weight or 1.0
if (iSeg == 1) then seg2,seg3 = 2,3
elseif iSeg == nSeg then seg2,seg3 = nSeg-1,nSeg-2
else seg2,seg3 = iSeg-1, iSeg+1 end
if goal < 0 then goal = -goal end
if goal < minlen then goal = minlen end
if goal > 0 then theta,phi = RandomSphere() end
local iBand = band.Add(iSeg,seg2,seg3,goal,theta,phi,atom)
if iBand > 0 then
band.SetGoalLength(iBand,goal)
band.SetStrength(iBand,weight)
end
return iBand
end
function MakeBBSCBand(iSeg)
local aa = structure.GetAminoAcid(iSeg)
if structure.GetSecondaryStructure(iSeg) ~= 'M' then
if option.backbone or ( aa == 'g' and option.pin) then RandomBand(iSeg,2,option.random,option.bandstr) end
if option.sidechains and aa ~= 'g' then
local first,last,disulfide = fsl.atom._IsTerminalTest(
structure.GetAminoAcid(iSeg), structure.GetAtomCount(iSeg), fsl.atom._IsDisulfideBonded(iSeg))
local offset = last and 5 or 4 -- number slider is added to (min 1) to get C-Beta atom
if option.sidechainatom+offset <= structure.GetAtomCount(iSeg) then
RandomBand(iSeg,option.sidechainatom+offset,option.random,option.bandstr)
end
end
end
end
function round(x,n)
return math.floor(0.5 + x * 10^n) / 10^n
end
function TableToRangeString()
tmp = ''
for i =1,#option.ranges do
if #option.ranges[i] == 1 then
tmp = tmp..tonumber(option.ranges[i])..' '
else
tmp = tmp..tonumber(option.ranges[i][1])..'-'..tonumber(option.ranges[i][2])..' '
end
end
return tmp
end
function PrintOptions()
print(title)
print('Only segments that meet all these restrictions')
print('will be banded:')
print('Range',TableToRangeString())
if not _allstructures then
tmp='Structure = '
if option.do_loop then tmp = tmp..'Loop ' end
if option.do_sheet then tmp = tmp..'Sheet ' end
if option.do_helix then tmp = tmp..'Helix' end
print(tmp)
end
if not _allfrozen then
if option.do_frozen then print('Is frozen or locked')
else print('Is not frozen or locked') end
end
if option.do_selected then print('Is selected') end
if option.uturn then print('Is detected as a U-Turn with cutoff',option.uradius) end
if option.skip > 0 then print('Every 1 out of',1+option.skip,'starting with number',option.skip_offset+1) end
print('Options:')
tmp = 'Banding '
if option.backbone then tmp = tmp..' Backbone' end
if option.sidechains then tmp = tmp..' Sidechains atom '..tostring(option.sidechainatom) end
print(tmp)
if not option.backbone and option.pin_glycine then print ('Glycines will be banded.') end
print('Band Length =',option.bandstr,'length =',option.random)
end
-- ==================== Begin Main
seed = (os.time() * 5779) % 10000
math.randomseed(seed)
_nselected,_nfrozen = 0,0
for i=1,nseg do
if selection.IsSelected(i) then _nselected = _nselected + 1 end
if freeze.IsFrozen(i) or structure.IsLocked(i) then _nfrozen = _nfrozen + 1 end
end
repeat
local rc = ShowMenu()
if rc == 0 then print('Cancelled') return end
_allstructures = AllStructures()
_allfrozen = AllFrozen()
_seglist = {}
if #option.ranges == 0 then
option.ranges = {{1,nseg}}
end
_nbands = 0
for i =1,#option.ranges do
if #option.ranges[i] == 1 then
_seglist[i] = DoSegment(option.ranges[i][1])
else
for j = option.ranges[i][1],option.ranges[i][2] do
_seglist[j] = DoSegment(j)
end
end
end
for i = 1,nseg do
if _seglist[i] then _nbands = _nbands + 1 end
end
if _nbands == 0 then
dialog.ShowMessageBox('No segments will be banded, check your inputs.','Input Error','Okay')
end
until _nbands > 0
PrintOptions()
for i = 1,nseg do
if _seglist[i] then MakeBBSCBand(i) end
end
print("Done.")