import {
SaguaroChain,
SaguaroPosition,
SaguaroRange,
ViewerActionManagerInterface,
ViewerModelMapManagerInterface
} from "../../StructureViewerInterface";
import {Viewer} from "@rcsb/rcsb-molstar/build/src/viewer";
import {DataContainer} from "../../../Utils/DataContainer";
import {Structure, StructureElement, StructureSelection} from "molstar/lib/mol-model/structure";
import {Expression} from "molstar/lib/commonjs/mol-script/language/expression";
import {MolScriptBuilder as MS} from "molstar/lib/mol-script/language/builder";
import {Script} from "molstar/lib/mol-script/script";
import {SetUtils} from "molstar/lib/mol-util/set";
import {Loci} from "molstar/lib/mol-model/loci";
import {StructureRef} from "molstar/lib/mol-plugin-state/manager/structure/hierarchy-state";
import {ColorTheme} from "molstar/lib/mol-theme/color";
import {StructureRepresentationRegistry} from "molstar/lib/mol-repr/structure/registry";
import {PresetProps} from "@rcsb/rcsb-molstar/build/src/viewer/helpers/preset";
import {Mat4} from "molstar/lib/mol-math/linear-algebra";
import {BuiltInTrajectoryFormat} from "molstar/lib/mol-plugin-state/formats/trajectory";
import {PluginState} from "molstar/lib/mol-plugin/state";
import {TrajectoryHierarchyPresetProvider} from "molstar/lib/mol-plugin-state/builder/structure/hierarchy-preset";
import {StateObject, StateObjectSelector} from "molstar/lib/mol-state";
import {PluginStateObject} from "molstar/lib/mol-plugin-state/objects";
import {StateTransformer} from "molstar/lib/mol-state/transformer";
import {
StructureRepresentationPresetProvider
} from "molstar/lib/mol-plugin-state/builder/structure/representation-preset";
// import { TmDetDescriptorCache } from "@rcsb/rcsb-molstar/build/src/tmdet-extension/prop";
// import { DebugUtil } from "../../../TmFv3DApp/tmdet-extension/debug-utils";
export enum LoadMethod {
loadPdbId = "loadPdbId",
loadStructureFromUrl = "loadStructureFromUrl",
loadSnapshotFromUrl = "loadSnapshotFromUrl",
loadStructureFromData = "loadStructureFromData"
}
export interface LoadMolstarInterface
{
loadMethod: LoadMethod;
loadParams: LoadParams
;
}
export type LoadMolstarReturnType = {
model?: StateObjectSelector>, StateObject>, any>>,
modelProperties?: StateObjectSelector>, StateObject>, any>>,
structure?: StateObjectSelector>, StateObject>, any>>,
structureProperties?: StateObjectSelector>, StateObject>, any>>,
representation?: StructureRepresentationPresetProvider.Result
};
interface LoadParams {
entryId?: string;
props?: PresetProps;
matrix?: Mat4;
url?: string,
format?: BuiltInTrajectoryFormat,
isBinary?: boolean,
type?: PluginState.SnapshotType,
data?: string | number[]
id?:string;
reprProvider?: TrajectoryHierarchyPresetProvider
;
params?:P;
}
export class MolstarActionManager
implements ViewerActionManagerInterface,L>{
private readonly viewer: Viewer;
private readonly innerSelectionFlag: DataContainer;
private readonly innerReprChangeFlag: DataContainer;
private readonly modelMapManager: ViewerModelMapManagerInterface,L>;
private readonly loadingFlag: DataContainer;
constructor(config:{viewer: Viewer;modelMapManager: ViewerModelMapManagerInterface,L>;innerSelectionFlag: DataContainer; innerReprChangeFlag: DataContainer; loadingFlag: DataContainer;}) {
this.viewer = config.viewer;
this.modelMapManager = config.modelMapManager;
this.innerSelectionFlag = config.innerSelectionFlag;
this.innerReprChangeFlag = config.innerReprChangeFlag;
this.loadingFlag = config.loadingFlag;
}
async load(loadConfig: LoadMolstarInterface): Promise;
async load(loadConfig: LoadMolstarInterface[]): Promise<(L|undefined)[]>;
async load(loadConfig: LoadMolstarInterface
|LoadMolstarInterface
[]): Promise{
this.loadingFlag.set(true);
const out: (L|undefined)[] = [];
for (const lC of (Array.isArray(loadConfig) ? loadConfig : [loadConfig])) {
if(checkLoadData(lC)) {
if (lC.loadMethod == LoadMethod.loadPdbId) {
const config: LoadParams = lC.loadParams as LoadParams
;
out.push(await this.viewer.loadPdbId(config.entryId!, {props: config.props, matrix: config.matrix, reprProvider: config.reprProvider, params: config.params}) as L|undefined);
} else if (lC.loadMethod == LoadMethod.loadStructureFromUrl) {
const config: LoadParams
= lC.loadParams as LoadParams
;
out.push(await this.viewer.loadStructureFromUrl(config.url!, config.format!, config.isBinary!,{props: config.props, matrix: config.matrix, reprProvider: config.reprProvider, params: config.params}) as L|undefined);
// DebugUtil.log('LOAD STRUCT. URL RESULT:', out[0]);
// const pdbtmDescriptor = TmDetDescriptorCache.get('1afo'); // TODO
// let matrix = undefined;
// if (pdbtmDescriptor) {
// matrix = DebugUtil.descriptorMxToMat4(pdbtmDescriptor!.additional_entry_annotations.membrane.transformation_matrix as any);
// }
// this.viewer.plugin.managers.structure.hierarchy.applyPreset()
// const selector = await this.viewer.plugin.builders.structure.hierarchy.applyPreset(
// trajectory, 'default', { representationPreset: TMDET_STRUCTURE_PRESET_ID as any });
// if (matrix && selector?.structureProperties) {
// const params = {
// transform: {
// name: 'matrix' as const,
// params: { data: matrix, transpose: false }
// }
// };
// setTimeout(async () => {
// console.log(this.viewer.plugin);
// // const b = this.plugin.state.data.build().to(selector.structureProperties)
// // .insert(StateTransforms.Model.TransformStructureConformation, params);
// // await this.plugin.runTask(this.plugin.state.data.updateTree(b));
// },
// 2000
// );
// }
} else if (lC.loadMethod == LoadMethod.loadSnapshotFromUrl) {
const config: LoadParams
= lC.loadParams as LoadParams
;
await this.viewer.loadSnapshotFromUrl(config.url!, config.type!);
} else if (lC.loadMethod == LoadMethod.loadStructureFromData) {
const config: LoadParams
= lC.loadParams as LoadParams
;
out.push(await this.viewer.loadStructureFromData(config.data!, config.format!, config.isBinary!, {props: config.props, matrix: config.matrix, reprProvider: config.reprProvider, params: config.params}) as L|undefined);
}
const trajectory = out[out.length-1];
if(trajectory)
this.modelMapManager.add(lC,trajectory);
}
}
this.loadingFlag.set(false);
return out.length == 1 ? out[0] : out;
}
async removeStructure(loadConfig: LoadMolstarInterface
|Array>): Promise{
loadConfig = Array.isArray(loadConfig) ? loadConfig : [loadConfig];
loadConfig.forEach(lC=>{
(Array.isArray(lC.loadParams) ? lC.loadParams : [lC.loadParams]).forEach(loadParams=>{
if(typeof loadParams.id === "string") {
const pdbStr: StructureRef | undefined = this.viewer.plugin.managers.structure.hierarchy.current.structures.find(s => s.properties?.cell?.obj?.data?.units[0]?.model?.id == this.modelMapManager.getModelId(loadParams.id!));
if (pdbStr) {
this.viewer.plugin.managers.structure.hierarchy.remove([pdbStr]);
}
}
});
})
}
public select(modelId:string, labelAsymId: string, begin: number, end: number, mode: 'select'|'hover', operation:'add'|'set', operatorName?:string): void;
public select(selection: Array, mode: 'select'|'hover', operation:'add'|'set'): void;
public select(selection: Array, mode: 'select'|'hover', operation:'add'|'set'): void;
public select(...args: any[]): void{
if(args[5] != undefined){
this.selectRange(args[0],args[1],args[2],args[3],args[4],args[5],args[6]);
}else if(Array.isArray(args[0]) && args[0].length > 0 && typeof args[0][0].position === 'number'){
this.selectSet(args[0],args[1],args[2]);
}else if(Array.isArray(args[0]) && args[0].length > 0 && typeof args[0][0].begin === 'number'){
this.selectMultipleRanges(args[0],args[1],args[2]);
}
}
private selectRange(modelId:string, labelAsymId: string, begin: number, end: number, mode: 'select'|'hover', operation:'add'|'set', operatorName?:string): void {
if(mode == null || mode === 'select') {
this.innerSelectionFlag.set(true);
}
this.viewer.select({modelId:this.modelMapManager.getModelId(modelId), labelAsymId: labelAsymId, labelSeqRange:{beg: begin, end:end}, operatorName: operatorName}, mode,operation);
this.innerSelectionFlag.set(false);
}
private selectSet(selection: Array, mode: 'select'|'hover', operation:'add'|'set'): void {
if(mode == null || mode === 'select') {
this.innerSelectionFlag.set(true);
}
this.viewer.select(selection.map(r=>({modelId: this.modelMapManager.getModelId(r.modelId), labelSeqId:r.position, labelAsymId: r.labelAsymId, operatorName: r.operatorName})), mode, operation);
this.innerSelectionFlag.set(false);
}
private selectMultipleRanges(selection: Array, mode: 'select'|'hover', operation:'add'|'set'): void {
if(mode == null || mode === 'select') {
this.innerSelectionFlag.set(true);
}
this.viewer.select(selection.map(r=>({modelId: this.modelMapManager.getModelId(r.modelId), labelAsymId: r.labelAsymId, labelSeqRange:{beg:r.begin, end: r.end}, operatorName: r.operatorName})), mode, operation);
this.innerSelectionFlag.set(false);
}
public async clear(): Promise{
await this.viewer.clear();
}
public clearSelection(mode:'select'|'hover', option?:SaguaroChain): void {
if(mode === 'select') {
this.viewer.clearFocus();
this.innerSelectionFlag.set(true);
}
if(option != null)
this.viewer.clearSelection(mode, {...option, modelId: this.modelMapManager.getModelId(option.modelId)});
else
this.viewer.clearSelection(mode);
this.innerSelectionFlag.set(false);
}
public setFocus(modelId: string, labelAsymId: string, begin: number, end: number, operatorName?:string): void{
this.viewer.setFocus({modelId: this.modelMapManager.getModelId(modelId), labelAsymId: labelAsymId, labelSeqRange:{beg:begin, end: end}, operatorName: operatorName});
}
public clearFocus(): void {
this.viewer.clearFocus();
}
public cameraFocus(modelId: string, labelAsymId: string, positions:Array, operatorName?:string): void;
public cameraFocus(modelId: string, labelAsymId: string, begin: number, end: number, operatorName?:string): void;
public cameraFocus(...args: any[]): void{
if(Array.isArray(args[2])){
this.focusPositions(args[0],args[1],args[2],args[3]);
}else{
this.focusRange(args[0],args[1],args[2],args[3],args[4]);
}
}
private focusRange(modelId: string, labelAsymId: string, begin: number, end: number, operatorName?:string): void{
const seqIds: Array = new Array();
for(let n = begin; n <= end; n++){
seqIds.push(n);
}
this.focusPositions(modelId, labelAsymId, seqIds, operatorName);
}
private focusPositions(modelId: string, labelAsymId: string, positions:Array, operatorName?:string): void{
const structure: Structure | undefined = getStructureWithModelId(this.viewer.plugin.managers.structure.hierarchy.current.structures, this.modelMapManager.getModelId(modelId));
if (structure == null) return;
const chainTests: Expression[] = [MS.core.rel.eq([MS.ammp('label_asym_id'), labelAsymId])];
if(operatorName)
chainTests.push(MS.core.rel.eq([operatorName, MS.acp('operatorName')]));
const sel: StructureSelection = Script.getStructureSelection(Q => Q.struct.generator.atomGroups({
'chain-test': Q.core.logic.and(chainTests),
'residue-test': Q.core.set.has([MS.set(...SetUtils.toArray(new Set(positions))), MS.ammp('label_seq_id')])
}), structure);
const loci: Loci = StructureSelection.toLociWithSourceUnits(sel);
if(!StructureElement.Loci.isEmpty(loci)) {
this.viewer.plugin.managers.camera.focusLoci(loci);
}else{
this.resetCamera();
}
}
public async createComponent(componentLabel: string, modelId:string, labelAsymId: string, begin: number, end : number, representationType: StructureRepresentationRegistry.BuiltIn, operatorName?:string): Promise;
public async createComponent(componentLabel: string, modelId:string, labelAsymId: string, representationType: StructureRepresentationRegistry.BuiltIn, operatorName?:string): Promise;
public async createComponent(componentLabel: string, residues: Array, representationType: StructureRepresentationRegistry.BuiltIn): Promise;
public async createComponent(componentLabel: string, residues: Array, representationType: StructureRepresentationRegistry.BuiltIn): Promise;
public async createComponent(...args: any[]): Promise {
this.innerReprChangeFlag.set(true);
await this.removeComponent(args[0]);
if(Array.isArray(args[1])){
if( args[1].length > 0 ) {
if(typeof args[1][0].position === "number"){
await this.viewer.createComponent(args[0], args[1].map(r=>({modelId: this.modelMapManager.getModelId(r.modelId), labelAsymId: r.labelAsymId, labelSeqId: r.position, operatorName: r.operatorName})), args[2]);
}else{
await this.viewer.createComponent(args[0], args[1].map(r=>({modelId: this.modelMapManager.getModelId(r.modelId), labelAsymId: r.labelAsymId, labelSeqRange:{beg:r.begin, end: r.end}, operatorName: r.operatorName})), args[2]);
}
}
}else if(args[5] != undefined){
await this.viewer.createComponent(args[0], {modelId: this.modelMapManager.getModelId(args[1]), labelAsymId: args[2], labelSeqRange:{beg:args[3], end:args[4]}, operatorName: args[6]}, args[5]);
}else{
await this.viewer.createComponent(args[0], {modelId: this.modelMapManager.getModelId(args[1]), labelAsymId:args[2], operatorName: args[4]}, args[3]);
}
this.innerReprChangeFlag.set(false);
}
public isComponent(componentLabel: string): boolean{
for(const c of this.viewer.plugin.managers.structure.hierarchy.currentComponentGroups){
for(const comp of c){
if(comp.cell.obj?.label === componentLabel) {
return true;
}
}
}
return false;
}
public async colorComponent(componentLabel: string, color: ColorTheme.BuiltIn): Promise{
for(const c of this.viewer.plugin.managers.structure.hierarchy.currentComponentGroups){
for(const comp of c){
if(comp.cell.obj?.label === componentLabel) {
await this.viewer.plugin.managers.structure.component.updateRepresentationsTheme([comp], { color: color });
return;
}
}
}
}
public getComponentSet(): Set{
const out: Set = new Set();
this.viewer.plugin.managers.structure.hierarchy.currentComponentGroups.forEach((c)=>{
for(const comp of c){
if(comp.cell.obj?.label != null && out.has(comp.cell.obj?.label)) {
break;
}else if(comp.cell.obj?.label != null){
out.add(comp.cell.obj?.label);
}
}
});
return out;
}
public async removeComponent(componentLabel?: string): Promise{
if(componentLabel == null){
this.viewer.plugin.managers.structure.hierarchy.currentComponentGroups.forEach(c=>this.viewer.plugin.managers.structure.hierarchy.remove(c))
}else{
await this.viewer.removeComponent(componentLabel);
}
}
public displayComponent(componentLabel: string): boolean;
public displayComponent(componentLabel: string, visibilityFlag: boolean): void;
public displayComponent(componentLabel: string, visibilityFlag?: boolean): void|boolean {
if(typeof visibilityFlag === 'boolean')
return this.changeComponentDisplay(componentLabel, visibilityFlag);
else
return this.getComponentDisplay(componentLabel);
}
private changeComponentDisplay(componentLabel: string, visibilityFlag: boolean): void{
this.innerReprChangeFlag.set(true);
for (const c of this.viewer.plugin.managers.structure.hierarchy.currentComponentGroups) {
for (const comp of c) {
if(comp.cell.obj?.label === componentLabel) {
if(!comp.cell.state.isHidden != visibilityFlag) {
this.viewer.plugin.managers.structure.component.toggleVisibility(c);
return void 0;
}
}
}
}
this.innerReprChangeFlag.set(false);
}
private getComponentDisplay(componentLabel: string): boolean | undefined{
for (const c of this.viewer.plugin.managers.structure.hierarchy.currentComponentGroups) {
for (const comp of c) {
if(comp.cell.obj?.label === componentLabel) {
return !comp.cell.state.isHidden;
}
}
}
return false;
}
public resetCamera(): void {
this.viewer.plugin.managers.camera.reset();
}
public async exportLoadedStructures(): Promise {
await this.viewer.exportLoadedStructures();
}
}
function getStructureWithModelId(structures: StructureRef[], modelId: string): Structure|undefined{
for(const structure of structures){
if(!structure.cell?.obj?.data?.units)
continue;
const unit = structure.cell.obj.data.units[0];
const id:string = unit.model.id;
if(id === modelId)
return structure.cell.obj.data
}
}
function checkLoadData(loadConfig: LoadMolstarInterface
): boolean{
const method: LoadMethod = loadConfig.loadMethod;
const params: LoadParams
| Array> = loadConfig.loadParams;
if( method == LoadMethod.loadPdbId ){
if(params instanceof Array || params.entryId == null)
throw loadConfig.loadMethod+": missing pdbId";
}else if( method == LoadMethod.loadStructureFromUrl ){
if(params instanceof Array || params.url == null || params.isBinary == null || params.format == null)
throw loadConfig.loadMethod+": arguments needed url, format, isBinary"
}else if( method == LoadMethod.loadSnapshotFromUrl ){
if(params instanceof Array || params.url == null || params.type == null)
throw loadConfig.loadMethod+": arguments needed url, type"
}else if( method == LoadMethod.loadStructureFromData ){
if(params instanceof Array || params.data == null || params.format == null || params.isBinary == null)
throw loadConfig.loadMethod+": arguments needed data, format, isBinary"
}
return true;
}