import { moorhen } from "../types/moorhen";
import { webGL } from "../types/mgWebGL";
import { guid } from "./utils";
import { AnyAction, Dispatch } from "@reduxjs/toolkit";
import { MoorhenMolecule } from "./MoorhenMolecule";
import { MoorhenMap } from "./MoorhenMap";
import { MoorhenColourRule } from "./MoorhenColourRule";
import { addCustomRepresentation, addMolecule, emptyMolecules } from "../store/moleculesSlice";
import { addMap, emptyMaps } from "../store/mapsSlice";
import { batch } from "react-redux";
import { setActiveMap } from "../store/generalStatesSlice";
import { setContourLevel, setMapAlpha, setMapColours, setMapRadius, setMapStyle, setNegativeMapColours, setPositiveMapColours } from "../store/mapContourSettingsSlice";
import { enableUpdatingMaps, setConnectedMoleculeMolNo, setFoFcMapMolNo, setReflectionMapMolNo, setTwoFoFcMapMolNo } from "../store/moleculeMapUpdateSlice";
import {
setBackgroundColor, setDepthBlurDepth, setDepthBlurRadius, setDoEdgeDetect, setDoPerspectiveProjection, setDoSSAO, setDoShadow,
setEdgeDetectDepthScale, setEdgeDetectDepthThreshold, setEdgeDetectNormalScale, setEdgeDetectNormalThreshold, setSsaoBias,
setSsaoRadius, setUseOffScreenBuffers
} from "../store/sceneSettingsSlice";
import { moorhensession } from "../protobuf/MoorhenSession";
import { ToolkitStore } from "@reduxjs/toolkit/dist/configureStore";
/**
* Represents a time capsule with session backups
* @constructor
* @param {React.RefObject<moorhen.Molecule[]>} moleculesRef - A react reference to the list of loaded molecules
* @param {React.RefObject<moorhen.Map[]>} mapsRef - A react reference to the list of loaded maps
* @param {React.RefObject<moorhen.Map>} activeMapRef - A react reference to the currently active map
* @param {React.RefObject<webGL.MGWebGL>} glRef - A react reference to the molecular graphics renderer
* @param {ToolkitStore} store - The Redux store
* @property {string} version - Version number of the current time capsule
* @property {boolean} busy - Indicates if time capsule is busy loading from local storage
* @property {boolean} disableBackups - Disable time capsule
* @property {number} maxBackupCount - Maximum number of automatic backups to store in local storage
* @property {number} modificationCountBackupThreshold - Number of modifications to trigger an automatic backup
* @property {function} onIsBusyChange - Callback function called whenever there's a change in the `this.busy` state
*/
export class MoorhenTimeCapsule implements moorhen.TimeCapsule {
moleculesRef: React.RefObject<moorhen.Molecule[]>;
mapsRef: React.RefObject<moorhen.Map[]>;
glRef: React.RefObject<webGL.MGWebGL>;
activeMapRef: React.RefObject<moorhen.Map>;
busy: boolean;
modificationCount: number;
modificationCountBackupThreshold: number;
maxBackupCount: number;
version: string;
disableBackups: boolean;
storageInstance: moorhen.LocalStorageInstance;
store: ToolkitStore;
onIsBusyChange: (arg0: boolean) => void;
getBackupLabel: (key: moorhen.backupKey) => string;
loadSessionData: (
sessionData: moorhen.backupSession,
monomerLibraryPath: string,
molecules: moorhen.Molecule[],
maps: moorhen.Map[],
commandCentre: React.RefObject<moorhen.CommandCentre>,
timeCapsuleRef: React.RefObject<moorhen.TimeCapsule>,
glRef: React.RefObject<webGL.MGWebGL>,
store: ToolkitStore,
dispatch: Dispatch<AnyAction>
) => Promise<number>;
loadSessionFromArrayBuffer: (
sessionArrayBuffer: ArrayBuffer,
monomerLibraryPath: string,
molecules: moorhen.Molecule[],
maps: moorhen.Map[],
commandCentre: React.RefObject<moorhen.CommandCentre>,
timeCapsuleRef: React.RefObject<moorhen.TimeCapsule>,
glRef: React.RefObject<webGL.MGWebGL>,
store: ToolkitStore,
dispatch: Dispatch<AnyAction>
) => Promise<number>;
loadSessionFromProtoMessage: (
sessionProtoMessage: any,
monomerLibraryPath: string,
molecules: moorhen.Molecule[],
maps: moorhen.Map[],
commandCentre: React.RefObject<moorhen.CommandCentre>,
timeCapsuleRef: React.RefObject<moorhen.TimeCapsule>,
glRef: React.RefObject<webGL.MGWebGL>,
store: ToolkitStore,
dispatch: Dispatch<AnyAction>
) => Promise<number>;
loadSessionFromJsonString: (
sessionDataString: string,
monomerLibraryPath: string,
molecules: moorhen.Molecule[],
maps: moorhen.Map[],
commandCentre: React.RefObject<moorhen.CommandCentre>,
timeCapsuleRef: React.RefObject<moorhen.TimeCapsule>,
glRef: React.RefObject<webGL.MGWebGL>,
store: ToolkitStore,
dispatch: Dispatch<AnyAction>
) => Promise<number>;
constructor(moleculesRef: React.RefObject<moorhen.Molecule[]>, mapsRef: React.RefObject<moorhen.Map[]>, activeMapRef: React.RefObject<moorhen.Map>, glRef: React.RefObject<webGL.MGWebGL>, store: ToolkitStore) {
this.store = store
this.moleculesRef = moleculesRef
this.mapsRef = mapsRef
this.glRef = glRef
this.activeMapRef = activeMapRef
this.busy = false
this.modificationCount = 0
this.modificationCountBackupThreshold = 5
this.maxBackupCount = 10
this.version = 'v21'
this.disableBackups = false
this.storageInstance = null
this.onIsBusyChange = null
}
/**
* Intiate the time capsule
* @returns {Promise<void>}
*/
init(): Promise<void> {
if (this.storageInstance) {
return this.checkVersion()
} else {
console.log('Time capsule storage instance has not been defined! Backups will be disabled...')
this.disableBackups = true
}
}
/**
* Toggles the time capsule disabled state
*/
toggleDisableBackups() {
this.disableBackups = !this.disableBackups
}
/**
* Sets instance attr. this.busy and calls this.onIsBusyChange()
* @param {boolea} newValue - The new value for the busy attr.
*/
setBusy(newValue: boolean) {
this.busy = newValue
if (this.onIsBusyChange) {
this.onIsBusyChange(this.busy)
}
}
/**
* Check if the current version is compatible with that stored in local storage
*/
async checkVersion(): Promise<void> {
const keyString = JSON.stringify({type: 'version'})
const storedVersion = await this.storageInstance.getItem(keyString)
if (!storedVersion || this.version !== storedVersion) {
await this.storageInstance.clear()
await this.storageInstance.setItem(keyString, this.version)
}
}
/**
* Update metadata files currently stored in the local storage. It might be required to call this function before
* using MoorhenTimeCapsule.fetchSession
* @returns {Promise<string[]>} Backup keys for the new metadata files that were created if any
*/
async updateDataFiles(): Promise<(string | void)[]> {
const allKeyStrings = await this.storageInstance.keys()
const allKeys: moorhen.backupKey[] = allKeyStrings.map((keyString: string) => JSON.parse(keyString))
const currentMtzFiles = allKeys.filter((key: moorhen.backupKey) => key.type === 'mtzData').map(key => key.name)
const currentMapData = allKeys.filter((key: moorhen.backupKey) => key.type === 'mapData').map(key => key.name)
let promises: Promise<string | void>[] = []
this.mapsRef.current.map(async (map: moorhen.Map) => {
const fileName = map.associatedReflectionFileName
if (fileName && !currentMtzFiles.includes(fileName)) {
const key = JSON.stringify({type: 'mtzData', name: fileName})
promises.push(
map.fetchReflectionData().then(reflectionData => {
this.createBackup(key, reflectionData.data.result.mtzData)
})
)
}
if (map.uniqueId && !currentMapData.includes(map.uniqueId)) {
const key = JSON.stringify({type: 'mapData', name: map.uniqueId})
promises.push(
map.getMap().then(mapData => {
this.createBackup(key, mapData.data.result.mapData)
})
)
}
})
return Promise.all(promises)
}
/**
* Fetch a backup session
* @param {boolean} [includeAdditionalMapData=true] - True if map data should be fetched and included in the resulting session
* @returns {Promise<moorhen.backupSession>} A backup for the current session
*/
async fetchSession (includeAdditionalMapData: boolean = true): Promise<moorhen.backupSession> {
this.setBusy(true)
const keyStrings = await this.storageInstance.keys()
const mtzFileNames = keyStrings.map((keyString: string) => JSON.parse(keyString)).filter((key: moorhen.backupKey) => key.type === 'mtzData').map((key: moorhen.backupKey) => key.name)
const mapNames = keyStrings.map((keyString: string) => JSON.parse(keyString)).filter((key: moorhen.backupKey) => key.type === 'mapData').map((key: moorhen.backupKey) => key.name)
const state = this.store.getState()
const promises = await Promise.all([
...this.moleculesRef.current.map(molecule => {
return molecule.getAtoms()
.then(result => {return {data: {message: 'get_atoms', result: {result: result}}}})
}),
...this.mapsRef.current.map(map => {
if (!includeAdditionalMapData) {
return Promise.resolve('map_data')
} else if (mapNames.includes(map.uniqueId)) {
return this.retrieveBackup(
JSON.stringify({
type: 'mapData',
name: map.uniqueId
})
).then(result => {return {data: {message: 'get_map', result: {mapData: result}}}})
} else {
return map.getMap()
}
}),
...this.mapsRef.current.map(map => {
if (!map.hasReflectionData || !includeAdditionalMapData) {
return Promise.resolve('reflection_data')
} else if (mtzFileNames.includes(map.associatedReflectionFileName)) {
return this.retrieveBackup(
JSON.stringify({
type: 'mtzData',
name: map.associatedReflectionFileName
})
).then(result => {return {data: {message: 'get_mtz_data', result: {mtzData: result}}}})
} else {
return map.fetchReflectionData()
}
})
])
let moleculeDataPromises: string[] = []
let mapDataPromises: Uint8Array[] = []
let reflectionDataPromises: Uint8Array[] = []
promises.forEach((promise: string | moorhen.WorkerResponse) => {
if (typeof promise === "string" && promise === 'reflection_data') {
reflectionDataPromises.push(null)
} else if (promise === 'map_data') {
mapDataPromises.push(null)
} else if (typeof promise === "object" && promise.data.message === "get_mtz_data") {
reflectionDataPromises.push(promise.data.result.mtzData)
} else if (typeof promise === "object" && promise.data.message === 'get_atoms') {
moleculeDataPromises.push(promise.data.result.result)
} else if (typeof promise === "object" && promise.data.message === 'get_map') {
mapDataPromises.push(new Uint8Array(promise.data.result.mapData))
} else {
console.log(`Unrecognised promise type when fetching session... ${promise}`)
}
})
const moleculeData: moorhen.moleculeSessionData[] = this.moleculesRef.current.map((molecule, index) => {
return {
name: molecule.name,
molNo: molecule.molNo,
coordFormat: molecule.coordsFormat,
coordString: moleculeDataPromises[index],
representations: molecule.representations.filter(item => item.visible).map(item => { return {
cid: item.cid,
style: item.style,
isCustom: item.isCustom,
colourRules: item.useDefaultColourRules ? null : item.colourRules.map(item => item.objectify()),
bondOptions: item.useDefaultBondOptions ? null : item.bondOptions,
m2tParams: item.useDefaultM2tParams ? null : item.m2tParams,
resEnvOptions: item.useDefaultResidueEnvironmentOptions ? null : item.residueEnvironmentOptions
}}),
defaultColourRules: molecule.defaultColourRules.map(item => item.objectify()),
defaultBondOptions: molecule.defaultBondOptions,
defaultM2tParams: molecule.defaultM2tParams,
defaultResEnvOptions: molecule.defaultResidueEnvironmentOptions,
connectedToMaps: molecule.connectedToMaps,
ligandDicts: molecule.ligandDicts,
symmetryOn: molecule.symmetryOn,
biomolOn: molecule.biomolOn,
symmetryRadius: molecule.symmetryRadius
}
})
const mapData: moorhen.mapDataSession[] = this.mapsRef.current.map((map, index) => {
return {
name: map.name,
molNo: map.molNo,
uniqueId: map.uniqueId,
mapData: mapDataPromises[index],
reflectionData: reflectionDataPromises[index],
showOnLoad: state.mapContourSettings.visibleMaps.includes(map.molNo),
contourLevel: state.mapContourSettings.contourLevels.find(item => item.molNo === map.molNo)?.contourLevel,
radius: state.mapContourSettings.mapRadii.find(item => item.molNo === map.molNo)?.radius,
rgba: {
a: state.mapContourSettings.mapAlpha.find(item => item.molNo === map.molNo)?.alpha,
mapColour: state.mapContourSettings.mapColours.find(item => item.molNo === map.molNo)?.rgb,
positiveDiffColour: state.mapContourSettings.positiveMapColours.find(item => item.molNo === map.molNo)?.rgb,
negativeDiffColour: state.mapContourSettings.negativeMapColours.find(item => item.molNo === map.molNo)?.rgb
},
style: state.mapContourSettings.mapStyles.find(item => item.molNo === map.molNo)?.style,
isDifference: map.isDifference,
selectedColumns: map.selectedColumns,
hasReflectionData: map.hasReflectionData,
associatedReflectionFileName: map.associatedReflectionFileName
}
})
const viewData: moorhen.viewDataSession = {
origin: this.glRef.current.origin,
backgroundColor: this.glRef.current.background_colour,
ambientLight: Array.from(this.glRef.current.light_colours_ambient) as [number, number, number, number],
diffuseLight: Array.from(this.glRef.current.light_colours_diffuse) as [number, number, number, number],
lightPosition: Array.from(this.glRef.current.light_positions) as [number, number, number, number],
specularLight: Array.from(this.glRef.current.light_colours_specular) as [number, number, number, number],
specularPower: this.glRef.current.specularPower,
fogStart: this.glRef.current.gl_fog_start,
fogEnd: this.glRef.current.gl_fog_end,
zoom: this.glRef.current.zoom,
doDrawClickedAtomLines: this.glRef.current.doDrawClickedAtomLines,
clipStart: (this.glRef.current.gl_clipPlane0[3] + this.glRef.current.fogClipOffset) * -1,
clipEnd: this.glRef.current.gl_clipPlane1[3] - this.glRef.current.fogClipOffset,
quat4: Array.from(this.glRef.current.myQuat),
doPerspectiveProjection: this.glRef.current.doPerspectiveProjection,
edgeDetection: {
enabled: this.glRef.current.doEdgeDetect,
depthScale: this.glRef.current.scaleDepth,
depthThreshold: this.glRef.current.depthThreshold,
normalScale: this.glRef.current.scaleNormal,
normalThreshold: this.glRef.current.normalThreshold
},
shadows: this.glRef.current.doShadow,
ssao: {
enabled: this.glRef.current.doSSAO,
radius: this.glRef.current.ssaoRadius,
bias: this.glRef.current.ssaoBias
},
blur: {
enabled: this.glRef.current.useOffScreenBuffers,
radius: this.glRef.current.blurSize,
depth: this.glRef.current.blurDepth
}
}
const session: moorhen.backupSession = {
includesAdditionalMapData: includeAdditionalMapData,
moleculeData: moleculeData,
mapData: mapData,
viewData: viewData,
activeMapIndex: this.mapsRef.current.findIndex(map => map.molNo === this.activeMapRef.current?.molNo),
version: this.version
}
return session
}
/**
* Add a modification to the modification counter and create a backup if the modification count has reached
* the modification threshold
* @returns {Promise<string>} Backup key if a backup was created
*/
async addModification(): Promise<string> {
this.modificationCount += 1
if (this.modificationCount >= this.modificationCountBackupThreshold && !this.disableBackups) {
this.setBusy(true)
this.modificationCount = 0
await this.updateDataFiles()
const session: moorhen.backupSession = await this.fetchSession(false)
const sessionString: string = JSON.stringify(session)
const key: moorhen.backupKey = {
dateTime: `${Date.now()}`,
type: 'automatic',
serNo: guid(),
molNames: this.moleculesRef.current.map(mol => mol.name),
mapNames: this.mapsRef.current.map(map => map.uniqueId),
mtzNames: this.mapsRef.current.filter(map => map.hasReflectionData).map(map => map.associatedReflectionFileName)
}
const keyString: string = JSON.stringify({
...key,
label: MoorhenTimeCapsule.getBackupLabel(key)
})
return this.createBackup(keyString, sessionString)
}
}
/**
* Remove the oldest automatic backup if the number of backups in the local storage has reached the threshold
*/
async cleanupIfFull(): Promise<void> {
const keyStrings: string[] = await this.storageInstance.keys()
const keys: moorhen.backupKey[] = keyStrings.map((keyString: string) => JSON.parse(keyString)).filter(key => key.type === 'automatic')
const sortedKeys = keys.sort((a, b) => { return parseInt(a.dateTime) - parseInt(b.dateTime) }).reverse()
if (sortedKeys.length - 1 >= this.maxBackupCount) {
const toRemoveCount = sortedKeys.length - this.maxBackupCount
const promises = sortedKeys.slice(-toRemoveCount).map(key => this.removeBackup(JSON.stringify(key)))
await Promise.all(promises)
}
}
/**
* Remove orphan metadata files with no backup session associated
*/
async cleanupUnusedDataFiles(): Promise<void> {
const allKeyStrings = await this.storageInstance.keys()
const allKeys: moorhen.backupKey[] = allKeyStrings.map((keyString: string) => JSON.parse(keyString))
const backupKeys = allKeys.filter((key: moorhen.backupKey) => ['automatic', 'manual'].includes(key.type))
const [ usedNames ] = [ ...backupKeys.map((key: moorhen.backupKey) => [...key.mtzNames, ...key.mapNames]) ]
await Promise.all(allKeys.filter((key: moorhen.backupKey) => ['mtzData', 'mapData'].includes(key.type)).map((key: moorhen.backupKey) => {
if (typeof usedNames === 'undefined' || !usedNames.includes(key.name)) {
return this.removeBackup(JSON.stringify(key))
}
return Promise.resolve()
}))
}
/**
* Create a session backup
* @param {string} key - Backup key
* @param {string} value - JSON structure with session data
* @returns {string} Backup key
*/
async createBackup(key: string, value: string): Promise<string> {
if (!this.disableBackups) {
try {
await this.storageInstance.setItem(key, value)
await this.cleanupIfFull()
this.setBusy(false)
return key
} catch (err) {
console.log(err)
}
}
}
/**
* Retrieve a session backup
* @param {string} key - Backup key
*/
async retrieveBackup(key: string): Promise<string | ArrayBuffer> {
try {
return await this.storageInstance.getItem(key)
} catch (err) {
console.log(err)
}
}
/**
* Retrieve the latest backup stored in local storage
* @returns {string} JSON structure with session data
*/
async retrieveLastBackup(): Promise<string | ArrayBuffer> {
try {
const sortedKeys = await this.getSortedKeys()
if (sortedKeys && sortedKeys.length > 0) {
const lastBackupKey = sortedKeys[sortedKeys.length - 1]
const backup = await this.retrieveBackup(JSON.stringify(lastBackupKey))
return backup
}
} catch (err) {
console.log(err)
}
}
/**
* Remove a specific backup
* @param {string} key - Backup key
*/
async removeBackup(key: string): Promise<void> {
try {
await this.storageInstance.removeItem(key)
} catch (err) {
console.log(err)
}
}
/**
* Remove all backups
*/
async dropAllBackups(): Promise<void> {
try {
await this.storageInstance.clear()
await this.storageInstance.setItem(JSON.stringify({type: 'version'}), this.version)
} catch (err) {
console.log(err)
}
}
/**
* Get backup keys sorted by date
* @returns {moorhen.backupKey[]} A list of backup keys
*/
async getSortedKeys(): Promise<moorhen.backupKey[]> {
const keyStrings = await this.storageInstance.keys()
const keys: moorhen.backupKey[] = keyStrings.map((keyString: string) => JSON.parse(keyString)).filter(key => ['automatic', 'manual'].includes(key.type))
const sortedKeys = keys.sort((a, b) => { return parseInt(a.dateTime) - parseInt(b.dateTime) }).reverse()
return sortedKeys
}
/**
* A static function that can be used to get the backup label for a given key object
* @param {moorhen.backupKey} key - An object with the backup key data
* @returns {string} A string corresponding with the backup label
*/
static getBackupLabel (key: moorhen.backupKey): string {
const dateOptions = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' } as const
const intK: number = parseInt(key.dateTime)
const date: Date = new Date(intK)
const dateString = `${date.toLocaleDateString(Intl.NumberFormat().resolvedOptions().locale, dateOptions)} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`
const moleculeNamesLabel: string = key.molNames.join(',').length > 10 ? key.molNames.join(',').slice(0, 8) + "..." : key.molNames.join(',')
return `${moleculeNamesLabel} -- ${dateString} -- ${key.type === 'automatic' ? 'AUTO' : 'MANUAL'}`
}
/**
* A static function that can be used to load a session data object
* @param {moorhen.backupSession} sessionData - An object containing session data
* @param {string} monomerLibraryPath - Path to the monomer library
* @param {moorhen.Molecule[]} molecules - State containing current molecules loaded in the session
* @param {moorhen.Map[]} maps - State containing current maps loaded in the session
* @param {React.RefObject<moorhen.CommandCentre>} commandCentre - React reference to the command centre
* @param {React.RefObject<moorhen.TimeCapsule>} timeCapsuleRef - React reference to the time capsule
* @param {React.RefObject<webGL.MGWebGL>} glRef - React reference to the webGL renderer
* @param {ToolkitStore} store - The Redux store
* @param {Dispatch<AnyAction>} dispatch - Dispatch method for the MoorhenReduxStore
* @returns {number} Returns -1 if there was an error loading the session otherwise 0
*/
static async loadSessionData(
sessionData: moorhen.backupSession,
monomerLibraryPath: string,
molecules: moorhen.Molecule[],
maps: moorhen.Map[],
commandCentre: React.RefObject<moorhen.CommandCentre>,
timeCapsuleRef: React.RefObject<moorhen.TimeCapsule>,
glRef: React.RefObject<webGL.MGWebGL>,
store: ToolkitStore,
dispatch: Dispatch<AnyAction>
): Promise<number> {
if (!sessionData) {
return -1
} else if (!Object.hasOwn(sessionData, 'version') || timeCapsuleRef.current.version !== sessionData.version) {
console.warn('Outdated session backup version, wont load...')
return -1
}
// Delete current scene
molecules.forEach(molecule => {
molecule.delete()
})
maps.forEach(map => {
map.delete()
})
batch(() => {
dispatch( emptyMolecules() )
dispatch( emptyMaps() )
})
// Load molecules stored in session from coords string
const newMoleculePromises = sessionData.moleculeData?.map(storedMoleculeData => {
const newMolecule = new MoorhenMolecule(commandCentre, glRef, store, monomerLibraryPath)
return newMolecule.loadToCootFromString(storedMoleculeData.coordString, storedMoleculeData.name)
}) || []
// Load maps stored in session
const newMapPromises = sessionData.mapData?.map(storedMapData => {
const newMap = new MoorhenMap(commandCentre, glRef, store)
if (sessionData.includesAdditionalMapData) {
return newMap.loadToCootFromMapData(
storedMapData.mapData,
storedMapData.name,
storedMapData.isDifference
)
} else {
newMap.uniqueId = storedMapData.uniqueId
return timeCapsuleRef.current.retrieveBackup(
JSON.stringify({
type: 'mapData',
name: storedMapData.uniqueId
})
).then(mapData => {
return newMap.loadToCootFromMapData(
mapData as Uint8Array,
storedMapData.name,
storedMapData.isDifference
)
})
}
}) || []
const loadPromises = await Promise.all([...newMoleculePromises, ...newMapPromises])
const newMolecules = loadPromises.filter(item => item.type === 'molecule') as moorhen.Molecule[]
const newMaps = loadPromises.filter(item => item.type === 'map') as moorhen.Map[]
// Draw the molecules with the styles stored in session (needs to be done sequentially due to colour rules)
for (let i = 0; i < newMolecules.length; i++) {
const molecule = newMolecules[i]
const storedMoleculeData = sessionData.moleculeData[i]
if (storedMoleculeData.ligandDicts) {
await Promise.all(Object.keys(storedMoleculeData.ligandDicts).map(compId => molecule.addDict(storedMoleculeData.ligandDicts[compId])))
}
molecule.defaultColourRules = storedMoleculeData.defaultColourRules.map(item => {
const colourRule = MoorhenColourRule.initFromDataObject(item, commandCentre, molecule)
return colourRule
})
if (storedMoleculeData.defaultBondOptions){
molecule.defaultBondOptions = storedMoleculeData.defaultBondOptions
}
if (storedMoleculeData.defaultM2tParams) {
molecule.defaultM2tParams = storedMoleculeData.defaultM2tParams
}
if (storedMoleculeData.defaultResEnvOptions) {
molecule.defaultResidueEnvironmentOptions = storedMoleculeData.defaultResEnvOptions
}
if (storedMoleculeData.representations) {
for (const item of storedMoleculeData.representations) {
const colourRules = !item.colourRules ? null : item.colourRules.map(item => {
const colourRule = MoorhenColourRule.initFromDataObject(item, commandCentre, molecule)
return colourRule
})
const representation = await molecule.addRepresentation(
item.style, item.cid, item.isCustom, colourRules, item.bondOptions, item.m2tParams, item.resEnvOptions
)
if (item.isCustom) {
dispatch( addCustomRepresentation(representation) )
}
}
}
if (storedMoleculeData.symmetryOn) {
molecule.setSymmetryRadius(storedMoleculeData.symmetryRadius)
await molecule.toggleSymmetry()
} else if (storedMoleculeData.biomolOn) {
molecule.toggleBiomolecule()
}
}
// Associate maps to reflection data
await Promise.all(
newMaps.map((map, index) => {
const storedMapData = sessionData.mapData[index]
if (sessionData.includesAdditionalMapData && storedMapData.reflectionData) {
return map.associateToReflectionData(
storedMapData.selectedColumns,
storedMapData.reflectionData
)
} else if (storedMapData.associatedReflectionFileName && storedMapData.selectedColumns) {
return timeCapsuleRef.current.retrieveBackup(
JSON.stringify({
type: 'mtzData',
name: storedMapData.associatedReflectionFileName
})
).then(reflectionData => {
return map.associateToReflectionData(
storedMapData.selectedColumns,
reflectionData as ArrayBuffer
)
})
}
return Promise.resolve()
})
)
// Add molecules
newMolecules.forEach(molecule => {
dispatch( addMolecule(molecule) )
})
// Add maps
newMaps.forEach((map, index) => {
const storedMapData = sessionData.mapData[index]
map.showOnLoad = storedMapData.showOnLoad
map.suggestedRadius = storedMapData.radius
map.suggestedContourLevel = storedMapData.contourLevel
batch(() => {
dispatch( setMapColours({molNo: map.molNo, rgb: storedMapData.rgba.mapColour}) )
dispatch( setNegativeMapColours({molNo: map.molNo, rgb: storedMapData.rgba.negativeDiffColour}) )
dispatch( setPositiveMapColours({molNo: map.molNo, rgb: storedMapData.rgba.positiveDiffColour}) )
dispatch( setMapRadius({molNo: map.molNo, radius: storedMapData.radius}) )
dispatch( setContourLevel({molNo: map.molNo, contourLevel: storedMapData.contourLevel}) )
dispatch( setMapAlpha({molNo: map.molNo, alpha: storedMapData.rgba.a}) )
dispatch( setMapStyle({molNo: map.molNo, style: storedMapData.style}) )
dispatch( addMap(map) )
})
})
// Set active map
if (sessionData.activeMapIndex !== undefined && sessionData.activeMapIndex !== -1){
dispatch( setActiveMap(newMaps[sessionData.activeMapIndex]) )
}
// Set camera details
glRef.current.setAmbientLightNoUpdate(...Object.values(sessionData.viewData.ambientLight) as [number, number, number])
glRef.current.setSpecularLightNoUpdate(...Object.values(sessionData.viewData.specularLight) as [number, number, number])
glRef.current.setDiffuseLightNoUpdate(...Object.values(sessionData.viewData.diffuseLight) as [number, number, number])
glRef.current.setLightPositionNoUpdate(...Object.values(sessionData.viewData.lightPosition) as [number, number, number])
glRef.current.setZoom(sessionData.viewData.zoom, false)
glRef.current.set_fog_range(sessionData.viewData.fogStart, sessionData.viewData.fogEnd, false)
glRef.current.set_clip_range(sessionData.viewData.clipStart, sessionData.viewData.clipEnd, false)
glRef.current.doDrawClickedAtomLines = sessionData.viewData.doDrawClickedAtomLines
glRef.current.setOrigin(sessionData.viewData.origin, false)
glRef.current.setQuat(sessionData.viewData.quat4)
glRef.current.specularPower = sessionData.viewData.specularPower
batch(() => {
dispatch(setBackgroundColor(sessionData.viewData.backgroundColor))
dispatch(setEdgeDetectDepthScale(sessionData.viewData.edgeDetection.depthScale))
dispatch(setEdgeDetectDepthThreshold(sessionData.viewData.edgeDetection.depthThreshold))
dispatch(setEdgeDetectNormalScale(sessionData.viewData.edgeDetection.normalScale))
dispatch(setEdgeDetectNormalThreshold(sessionData.viewData.edgeDetection.normalThreshold))
dispatch(setDoEdgeDetect(sessionData.viewData.edgeDetection.enabled))
dispatch(setDoShadow(sessionData.viewData.shadows))
dispatch(setDoSSAO(sessionData.viewData.ssao.enabled))
dispatch(setSsaoBias(sessionData.viewData.ssao.bias))
dispatch(setSsaoRadius(sessionData.viewData.ssao.radius))
dispatch(setUseOffScreenBuffers(sessionData.viewData.blur.enabled))
dispatch(setDepthBlurDepth(sessionData.viewData.blur.depth))
dispatch(setDepthBlurRadius(sessionData.viewData.blur.radius))
dispatch(setUseOffScreenBuffers(sessionData.viewData.blur.enabled))
dispatch(setDoPerspectiveProjection(sessionData.viewData.doPerspectiveProjection ?? false))
})
// Set connected maps and molecules if any
const connectedMoleculeIndex = sessionData.moleculeData?.findIndex(molecule => molecule.connectedToMaps?.length > 0)
if (sessionData.mapData && sessionData.moleculeData && connectedMoleculeIndex !== -1) {
const oldConnectedMolecule = sessionData.moleculeData[connectedMoleculeIndex]
const molecule = newMolecules[connectedMoleculeIndex].molNo
const [reflectionMap, twoFoFcMap, foFcMap] = oldConnectedMolecule.connectedToMaps.map(item => newMaps[sessionData.mapData.findIndex(map => map.molNo === item)].molNo)
const connectMapsArgs = [molecule, reflectionMap, twoFoFcMap, foFcMap]
const sFcalcArgs = [molecule, twoFoFcMap, foFcMap, reflectionMap]
await commandCentre.current.cootCommand({
command: 'connect_updating_maps',
commandArgs: connectMapsArgs,
returnType: 'status'
}, false)
await commandCentre.current.cootCommand({
command: 'sfcalc_genmaps_using_bulk_solvent',
commandArgs: sFcalcArgs,
returnType: 'status'
}, false)
batch(() => {
dispatch( setFoFcMapMolNo(foFcMap) )
dispatch( setTwoFoFcMapMolNo(twoFoFcMap) )
dispatch( setReflectionMapMolNo(reflectionMap) )
dispatch( setConnectedMoleculeMolNo(molecule) )
dispatch( enableUpdatingMaps() )
})
}
return 0
}
/**
* A static function that can be used to load a session from an array buffer
* @param {ArrayBuffer} sessionArrayBuffer - An object containing session data
* @param {string} monomerLibraryPath - Path to the monomer library
* @param {moorhen.Molecule[]} molecules - State containing current molecules loaded in the session
* @param {moorhen.Map[]} maps - State containing current maps loaded in the session
* @param {React.RefObject<moorhen.CommandCentre>} commandCentre - React reference to the command centre
* @param {React.RefObject<moorhen.TimeCapsule>} timeCapsuleRef - React reference to the time capsule
* @param {React.RefObject<webGL.MGWebGL>} glRef - React reference to the webGL renderer
* @param {ToolkitStore} store - The Redux store
* @param {Dispatch<AnyAction>} dispatch - Dispatch method for the MoorhenReduxStore
* @returns {number} Returns -1 if there was an error loading the session otherwise 0
*/
static async loadSessionFromArrayBuffer(
sessionArrayBuffer: ArrayBuffer,
monomerLibraryPath: string,
molecules: moorhen.Molecule[],
maps: moorhen.Map[],
commandCentre: React.RefObject<moorhen.CommandCentre>,
timeCapsuleRef: React.RefObject<moorhen.TimeCapsule>,
glRef: React.RefObject<webGL.MGWebGL>,
store: ToolkitStore,
dispatch: Dispatch<AnyAction>
): Promise<number> {
timeCapsuleRef.current.setBusy(true)
const bytes = new Uint8Array(sessionArrayBuffer)
const sessionMessage = moorhensession.Session.decode(bytes)
const status = await MoorhenTimeCapsule.loadSessionFromProtoMessage(sessionMessage, monomerLibraryPath, molecules, maps, commandCentre, timeCapsuleRef, glRef, store, dispatch)
timeCapsuleRef.current.setBusy(false)
return status
}
/**
* A static function that can be used to load a session from a protobuf message
* @param {string} sessionProtoMessage - A protobuf message for the object containing session data
* @param {string} monomerLibraryPath - Path to the monomer library
* @param {moorhen.Molecule[]} molecules - State containing current molecules loaded in the session
* @param {moorhen.Map[]} maps - State containing current maps loaded in the session
* @param {React.RefObject<moorhen.CommandCentre>} commandCentre - React reference to the command centre
* @param {React.RefObject<moorhen.TimeCapsule>} timeCapsuleRef - React reference to the time capsule
* @param {React.RefObject<webGL.MGWebGL>} glRef - React reference to the webGL renderer
* @param {ToolkitStore} store - The Redux store
* @param {Dispatch<AnyAction>} dispatch - Dispatch method for the MoorhenReduxStore
* @returns {number} Returns -1 if there was an error loading the session otherwise 0
*/
static async loadSessionFromProtoMessage(
sessionProtoMessage: any,
monomerLibraryPath: string,
molecules: moorhen.Molecule[],
maps: moorhen.Map[],
commandCentre: React.RefObject<moorhen.CommandCentre>,
timeCapsuleRef: React.RefObject<moorhen.TimeCapsule>,
glRef: React.RefObject<webGL.MGWebGL>,
store: ToolkitStore,
dispatch: Dispatch<AnyAction>
): Promise<number> {
timeCapsuleRef.current.setBusy(true)
const sessionData = moorhensession.Session.toObject(sessionProtoMessage) as moorhen.backupSession
const status = await MoorhenTimeCapsule.loadSessionData(sessionData, monomerLibraryPath, molecules, maps, commandCentre, timeCapsuleRef, glRef, store, dispatch)
timeCapsuleRef.current.setBusy(false)
return status
}
/**
* A static function that can be used to load a session from a JSON string representation of a session data object
* @param {string} sessionDataString - A JSON string representation of the object containing session data
* @param {string} monomerLibraryPath - Path to the monomer library
* @param {moorhen.Molecule[]} molecules - State containing current molecules loaded in the session
* @param {moorhen.Map[]} maps - State containing current maps loaded in the session
* @param {React.RefObject<moorhen.CommandCentre>} commandCentre - React reference to the command centre
* @param {React.RefObject<moorhen.TimeCapsule>} timeCapsuleRef - React reference to the time capsule
* @param {React.RefObject<webGL.MGWebGL>} glRef - React reference to the webGL renderer
* @param {ToolkitStore} store - The Redux store
* @param {Dispatch<AnyAction>} dispatch - Dispatch method for the MoorhenReduxStore
* @returns {number} Returns -1 if there was an error loading the session otherwise 0
*/
static async loadSessionFromJsonString(
sessionDataString: string,
monomerLibraryPath: string,
molecules: moorhen.Molecule[],
maps: moorhen.Map[],
commandCentre: React.RefObject<moorhen.CommandCentre>,
timeCapsuleRef: React.RefObject<moorhen.TimeCapsule>,
glRef: React.RefObject<webGL.MGWebGL>,
store: ToolkitStore,
dispatch: Dispatch<AnyAction>
): Promise<number> {
timeCapsuleRef.current.setBusy(true)
const sessionData: moorhen.backupSession = JSON.parse(sessionDataString)
const status = await MoorhenTimeCapsule.loadSessionData(sessionData, monomerLibraryPath, molecules, maps, commandCentre, timeCapsuleRef, glRef, store, dispatch)
timeCapsuleRef.current.setBusy(false)
return status
}
}
Source