Icon representing a recipe

Recipe: Copy Bands/Freeze/Selections v1.5

created by Serca

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()

Comments