Profile
- Name
- Copy Bands/Freeze/Selections v1.5
- ID
- 109360
- Shared with
- Public
- Parent
- Copy Bands/Freeze/Selections v1.0
- Children
- None
- Created on
- February 18, 2026 at 07:26 AM UTC
- Updated on
- February 19, 2026 at 09:41 AM UTC
- Description
Serialize and Copy Bands/Freezes/Selections/Cuts/SecondaryStructure
Best for
Code
--Copy Bands.Freeze.Selections--
version = "1.5"
print ("version",version)
function serializeBands()
local includeBands = true
local includeFreeze = true
local includeSelection = true
local includeSecondaryStructure = true
local includeCuts = true
while true do
local count = band.GetCount()
local bands = {}
local skippedSpacebands = {}
if includeBands and count > 0 then
for i = 1, count do
local residueBase = band.GetResidueBase(i)
local residueEnd = band.GetResidueEnd(i)
local atomBase = band.GetAtomBase(i)
local atomEnd = band.GetAtomEnd(i)
local strength = band.GetStrength(i)
local goalLength = band.GetGoalLength(i)
local isEnabled = band.IsEnabled(i)
if residueEnd == 0 then
table.insert(skippedSpacebands, {
bandIndex = i,
residueBase = residueBase,
atomBase = atomBase,
atomEnd = atomEnd
})
else
table.insert(bands, {
residueBase = residueBase,
residueEnd = residueEnd,
atomBase = atomBase,
atomEnd = atomEnd,
strength = strength,
goalLength = goalLength,
isEnabled = isEnabled
})
end
end
if #skippedSpacebands > 0 then
printSpacebandWarning("SERIALIZE", skippedSpacebands)
end
end
local frozenSegments = includeFreeze and collectFrozenSegments() or {}
local selectedSegments = includeSelection and collectSelectedSegments() or {}
local secondaryStructureSegments = includeSecondaryStructure and collectSecondaryStructureSegments() or {}
local cuts = includeCuts and collectCuts() or {}
local serializedText = serializeSnapshot(
bands,
frozenSegments,
selectedSegments,
secondaryStructureSegments,
cuts,
includeFreeze,
includeSelection,
includeSecondaryStructure,
includeCuts
)
if serializedText ~= "" then
print("Current configuration:\n" .. serializeSnapshot(
bands,
frozenSegments,
selectedSegments,
secondaryStructureSegments,
cuts,
includeFreeze,
includeSelection,
includeSecondaryStructure,
includeCuts,
true
))
print("Serialized data:\n" .. serializedText)
else
print("Nothing selected to serialize.")
end
local ask = dialog.CreateDialog("Copy/Restore Bands")
ask.includeBands = dialog.AddCheckbox("Include bands", includeBands)
ask.includeFreeze = dialog.AddCheckbox("Include freeze", includeFreeze)
ask.includeSelection = dialog.AddCheckbox("Include selections", includeSelection)
ask.includeSecondaryStructure = dialog.AddCheckbox("Include secondary structure", includeSecondaryStructure)
ask.includeCuts = dialog.AddCheckbox("Include cuts", includeCuts)
ask.inputBox = dialog.AddTextbox("Bands data:", serializedText)
ask.cancel = dialog.AddButton("Cancel", 0)
ask.recalc = dialog.AddButton("Recalc", 1)
ask.restore = dialog.AddButton("Restore", -1)
local returnVal = dialog.Show(ask)
includeBands = ask.includeBands.value
includeFreeze = ask.includeFreeze.value
includeSelection = ask.includeSelection.value
includeSecondaryStructure = ask.includeSecondaryStructure.value
includeCuts = ask.includeCuts.value
if returnVal == -1 then
local input = ask.inputBox.value
local bandsToRestore, frozenToRestore, selectedToRestore, secondaryStructureToRestore, cutsToRestore = deserialize(input)
local skippedSpacebandsOnRestore = {}
if includeBands and #bandsToRestore > 0 then
for _, b in ipairs(bandsToRestore) do
if b.residueEnd == 0 then
table.insert(skippedSpacebandsOnRestore, {
bandIndex = -1,
residueBase = b.residueBase,
atomBase = b.atomBase,
atomEnd = b.atomEnd
})
end
end
end
if #skippedSpacebandsOnRestore > 0 then
printSpacebandWarning("RESTORE", skippedSpacebandsOnRestore)
end
if includeBands and #bandsToRestore > 0 then
print("Restoring bands:")
for i, b in ipairs(bandsToRestore) do
print(string.format("Band %d: residues %d-%d, atoms %d-%d, strength=%.2f, length=%.2f, enabled=%s",
i, b.residueBase, b.residueEnd, b.atomBase, b.atomEnd,
b.strength, b.goalLength, tostring(b.isEnabled)))
end
end
if includeFreeze and #frozenToRestore > 0 then
print(string.format("Restoring freeze configuration for %d segments.", #frozenToRestore))
end
if includeSelection and #selectedToRestore > 0 then
print(string.format("Restoring selection for %d segments.", #selectedToRestore))
end
if includeSecondaryStructure and #secondaryStructureToRestore > 0 then
print(string.format("Restoring secondary structure for %d segments.", #secondaryStructureToRestore))
end
if includeCuts and #cutsToRestore > 0 then
print(string.format("Restoring %d cuts.", #cutsToRestore))
end
undo.SetUndo(false)
if includeBands then
for _, bandInfo in ipairs(bandsToRestore) do
if bandInfo.residueEnd ~= 0 then
local bandId = band.AddBetweenSegments(bandInfo.residueBase, bandInfo.residueEnd, bandInfo.atomBase, bandInfo.atomEnd)
band.SetStrength(bandId, bandInfo.strength)
band.SetGoalLength(bandId, bandInfo.goalLength)
if not bandInfo.isEnabled then
band.Disable(bandId)
end
end
end
end
if includeFreeze and #frozenToRestore > 0 then
restoreFrozenSegments(frozenToRestore)
end
if includeSelection and #selectedToRestore > 0 then
restoreSelectedSegments(selectedToRestore)
end
if includeSecondaryStructure and #secondaryStructureToRestore > 0 then
restoreSecondaryStructureSegments(secondaryStructureToRestore)
end
if includeCuts and #cutsToRestore > 0 then
restoreCuts(cutsToRestore)
end
undo.SetUndo(true)
if includeBands then
print("Total bands restored:", band.GetCount())
end
break
elseif returnVal == 1 then
-- recalc: loop continues to refresh dialog with current checkbox settings
else
break
end
end
end
function printSpacebandWarning(stage, spacebands)
local count = spacebands and #spacebands or 0
if count == 0 then
return
end
print("")
print("############################################################")
print("############### SPACEBANDS DETECTED ###############")
print("############################################################")
print(string.format("%s: found %d spaceband(s).", tostring(stage), count))
print("Foldit API does not return spaceband endpoint coordinates.")
print("These spacebands are NOT copied and NOT serialized by this script.")
local maxToShow = math.min(count, 10)
for i = 1, maxToShow do
local sb = spacebands[i]
print(string.format(" spaceband #%d: bandIndex=%d residueBase=%d atomBase=%d atomEnd=%d",
i, sb.bandIndex or -1, sb.residueBase or -1, sb.atomBase or -1, sb.atomEnd or -1))
end
if count > maxToShow then
print(string.format(" ... and %d more spaceband(s).", count - maxToShow))
end
print("############################################################")
print("")
end
function serializeBandsData(tbl, for_console)
if #tbl == 0 then
return ""
end
local serialized = "{"
for i, band in ipairs(tbl) do
if for_console then
serialized = serialized .. string.format("\n {residueBase=%d, residueEnd=%d, atomBase=%d, atomEnd=%d,\n strength=%.2f, goalLength=%.2f, isEnabled=%s}%s",
band.residueBase, band.residueEnd, band.atomBase, band.atomEnd,
band.strength, band.goalLength, tostring(band.isEnabled),
i == #tbl and "" or ","
)
else
serialized = serialized .. string.format("{rb=%d,re=%d,ab=%d,ae=%d,s=%.2f,gl=%.2f,en=%s},",
band.residueBase, band.residueEnd, band.atomBase, band.atomEnd,
band.strength, band.goalLength, tostring(band.isEnabled)
)
end
end
return (for_console and serialized .. "\n}" or serialized:gsub(",$", "") .. "}")
end
function serializeFreezeData(tbl, for_console)
if #tbl == 0 then
return ""
end
local serialized = "{"
for i, seg in ipairs(tbl) do
if for_console then
serialized = serialized .. string.format("\n {segment=%d, backbone=%s, sidechain=%s}%s",
seg.idx, tostring(seg.backbone), tostring(seg.sidechain),
i == #tbl and "" or ",")
else
serialized = serialized .. string.format("{i=%d,bb=%s,sc=%s},",
seg.idx, tostring(seg.backbone), tostring(seg.sidechain))
end
end
return (for_console and serialized .. "\n}" or serialized:gsub(",$", "") .. "}")
end
function serializeSelectionData(tbl, for_console)
if #tbl == 0 then
return ""
end
local serialized = "{"
for i, seg in ipairs(tbl) do
if for_console then
serialized = serialized .. string.format("\n {segment=%d}%s",
seg.idx, i == #tbl and "" or ",")
else
serialized = serialized .. string.format("{i=%d},", seg.idx)
end
end
return (for_console and serialized .. "\n}" or serialized:gsub(",$", "") .. "}")
end
function serializeSecondaryStructureData(tbl, for_console)
if #tbl == 0 then
return ""
end
local serialized = "{"
for i, seg in ipairs(tbl) do
if for_console then
serialized = serialized .. string.format("\n {segment=%d, ss=%s}%s",
seg.idx, tostring(seg.ss), i == #tbl and "" or ",")
else
serialized = serialized .. string.format("{i=%d,ss=%s},",
seg.idx, tostring(seg.ss))
end
end
return (for_console and serialized .. "\n}" or serialized:gsub(",$", "") .. "}")
end
function serializeCutsData(tbl, for_console)
if #tbl == 0 then
return ""
end
local serialized = "{"
for i, cut in ipairs(tbl) do
if for_console then
serialized = serialized .. string.format("\n {cut=%d}%s",
cut.idx, i == #tbl and "" or ",")
else
serialized = serialized .. string.format("{i=%d},", cut.idx)
end
end
return (for_console and serialized .. "\n}" or serialized:gsub(",$", "") .. "}")
end
function serializeSnapshot(bands, frozenSegments, selectedSegments, secondaryStructureSegments, cuts, includeFreeze, includeSelection, includeSecondaryStructure, includeCuts, for_console)
local parts = {}
local bandsStr = serializeBandsData(bands, for_console)
if bandsStr ~= "" then
table.insert(parts, bandsStr)
end
if includeFreeze and frozenSegments and #frozenSegments > 0 then
local freezeStr = serializeFreezeData(frozenSegments, for_console)
if for_console then
table.insert(parts, "Freeze:" .. freezeStr)
else
table.insert(parts, "freeze=" .. freezeStr)
end
end
if includeSelection and selectedSegments and #selectedSegments > 0 then
local selectionStr = serializeSelectionData(selectedSegments, for_console)
if for_console then
table.insert(parts, "Selection:" .. selectionStr)
else
table.insert(parts, "sel=" .. selectionStr)
end
end
if includeSecondaryStructure and secondaryStructureSegments and #secondaryStructureSegments > 0 then
local ssStr = serializeSecondaryStructureData(secondaryStructureSegments, for_console)
if for_console then
table.insert(parts, "SecondaryStructure:" .. ssStr)
else
table.insert(parts, "ss=" .. ssStr)
end
end
if includeCuts and cuts and #cuts > 0 then
local cutsStr = serializeCutsData(cuts, for_console)
if for_console then
table.insert(parts, "Cuts:" .. cutsStr)
else
table.insert(parts, "cuts=" .. cutsStr)
end
end
if for_console then
return table.concat(parts, "\n")
end
return table.concat(parts, "|")
end
function collectFrozenSegments()
local frozen = {}
local segmentCount = structure.GetCount()
for i = 1, segmentCount do
local backboneFrozen, sidechainFrozen = freeze.IsFrozen(i)
if backboneFrozen or sidechainFrozen then
table.insert(frozen, {
idx = i,
backbone = backboneFrozen,
sidechain = sidechainFrozen
})
end
end
return frozen
end
function collectSelectedSegments()
local selected = {}
local segmentCount = structure.GetCount()
for i = 1, segmentCount do
if selection.IsSelected(i) then
table.insert(selected, { idx = i })
end
end
return selected
end
function collectSecondaryStructureSegments()
local ss = {}
local segmentCount = structure.GetCount()
for i = 1, segmentCount do
local value = structure.GetSecondaryStructure(i)
if value and value ~= "" then
table.insert(ss, { idx = i, ss = tostring(value) })
end
end
return ss
end
function collectCuts()
local cuts = {}
if not structure.GetCuts then
return cuts
end
local ok, result = pcall(structure.GetCuts)
if not ok or type(result) ~= "table" then
return cuts
end
for _, cutIdx in ipairs(result) do
local idx = tonumber(cutIdx)
if idx then
table.insert(cuts, { idx = idx })
end
end
table.sort(cuts, function(a, b) return a.idx < b.idx end)
return cuts
end
function deserialize(str)
local bands = {}
local frozen = {}
local selected = {}
local secondaryStructure = {}
local cuts = {}
if not str or str == "" then
return bands, frozen, selected, secondaryStructure, cuts
end
local cleaned = str:gsub("%s+", "")
local bandsStr = ""
for token in cleaned:gmatch("[^|]+") do
if token:sub(1, 7) == "freeze=" then
frozen = deserializeFreeze(token:sub(8))
elseif token:sub(1, 4) == "sel=" then
selected = deserializeSelection(token:sub(5))
elseif token:sub(1, 3) == "ss=" then
secondaryStructure = deserializeSecondaryStructure(token:sub(4))
elseif token:sub(1, 5) == "cuts=" then
cuts = deserializeCuts(token:sub(6))
elseif bandsStr == "" then
bandsStr = token
end
end
if bandsStr ~= "" then
bands = deserializeBandsOnly(bandsStr)
end
return bands, frozen, selected, secondaryStructure, cuts
end
function deserializeBandsOnly(str)
local bands = {}
str = str:gsub("%s+", ""):gsub("^{", ""):gsub("}$", "")
for item in str:gmatch("{(.-)}") do
local vals = {
rb = item:match("r[eb]=(%d+)"),
re = item:match("r[eE]=(%d+)"),
ab = item:match("a[bt]=(%d+)"),
ae = item:match("a[eE]=(%d+)"),
s = item:match("s[t]?=([%d%.]+)"),
gl = item:match("g[l]?=([%d%.]+)"),
en = item:match("e[n]?=(%a+)")
}
if vals.rb then
table.insert(bands, {
residueBase = tonumber(vals.rb),
residueEnd = tonumber(vals.re),
atomBase = tonumber(vals.ab),
atomEnd = tonumber(vals.ae),
strength = tonumber(vals.s),
goalLength = tonumber(vals.gl),
isEnabled = vals.en == "true"
})
end
end
return bands
end
function deserializeFreeze(str)
local frozen = {}
str = str:gsub("%s+", ""):gsub("^{", ""):gsub("}$", ""):gsub("^freeze=", "")
for item in str:gmatch("{(.-)}") do
local idx = item:match("i=(%d+)") or item:match("idx=(%d+)")
local bb = item:match("bb=(%a+)")
local sc = item:match("sc=(%a+)")
if idx then
table.insert(frozen, {
idx = tonumber(idx),
backbone = bb == "true",
sidechain = sc == "true"
})
end
end
return frozen
end
function deserializeSelection(str)
local selected = {}
str = str:gsub("%s+", ""):gsub("^{", ""):gsub("}$", ""):gsub("^sel=", "")
for item in str:gmatch("{(.-)}") do
local idx = item:match("i=(%d+)") or item:match("idx=(%d+)")
if idx then
table.insert(selected, { idx = tonumber(idx) })
end
end
return selected
end
function deserializeSecondaryStructure(str)
local secondaryStructure = {}
str = str:gsub("%s+", ""):gsub("^{", ""):gsub("}$", ""):gsub("^ss=", "")
for item in str:gmatch("{(.-)}") do
local idx = item:match("i=(%d+)") or item:match("idx=(%d+)")
local ss = item:match("ss=([%a])")
if idx and ss then
table.insert(secondaryStructure, { idx = tonumber(idx), ss = ss })
end
end
return secondaryStructure
end
function deserializeCuts(str)
local cuts = {}
str = str:gsub("%s+", ""):gsub("^{", ""):gsub("}$", ""):gsub("^cuts=", "")
for item in str:gmatch("{(.-)}") do
local idx = item:match("i=(%d+)") or item:match("idx=(%d+)")
if idx then
table.insert(cuts, { idx = tonumber(idx) })
end
end
return cuts
end
function restoreFrozenSegments(frozenSegments)
if not frozenSegments or #frozenSegments == 0 then
return
end
--freeze.UnfreezeAll()
local segmentCount = structure.GetCount()
for _, seg in ipairs(frozenSegments) do
if seg.idx <= segmentCount then
freeze.Freeze(seg.idx, seg.backbone, seg.sidechain)
end
end
end
function restoreSelectedSegments(selectedSegments)
if not selectedSegments or #selectedSegments == 0 then
return
end
--selection.DeselectAll()
local segmentCount = structure.GetCount()
for _, seg in ipairs(selectedSegments) do
if seg.idx <= segmentCount then
selection.Select(seg.idx)
end
end
end
function restoreSecondaryStructureSegments(secondaryStructureSegments)
if not secondaryStructureSegments or #secondaryStructureSegments == 0 then
return
end
local segmentCount = structure.GetCount()
for _, seg in ipairs(secondaryStructureSegments) do
if seg.idx <= segmentCount and seg.ss and seg.ss ~= "" then
pcall(structure.SetSecondaryStructure, seg.idx, seg.ss)
end
end
end
function restoreCuts(cuts)
if not cuts or #cuts == 0 then
return
end
local segmentCount = structure.GetCount()
local maxCutIndex = math.max(0, segmentCount - 1)
local existingCuts = {}
for _, cut in ipairs(collectCuts()) do
existingCuts[cut.idx] = true
end
for _, cut in ipairs(cuts) do
if cut.idx >= 1 and cut.idx <= maxCutIndex and not existingCuts[cut.idx] then
local ok = pcall(structure.InsertCut, cut.idx)
if ok then
existingCuts[cut.idx] = true
end
end
end
end
-- Main function
function CopyBands()
serializeBands()
end
-- Call function when script starts
CopyBands()