MolstarPlugin.ts 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. import {Viewer, ViewerProps} from '@rcsb/rcsb-molstar/build/src/viewer';
  2. import {PresetProps} from '@rcsb/rcsb-molstar/build/src/viewer/helpers/preset';
  3. import {
  4. ChainInfo, OperatorInfo, SaguaroChain,
  5. SaguaroPluginInterface,
  6. SaguaroPluginModelMapType, SaguaroPosition, SaguaroRange, SaguaroSet
  7. } from "../SaguaroPluginInterface";
  8. import {PluginContext} from "molstar/lib/mol-plugin/context";
  9. import {Loci} from "molstar/lib/mol-model/loci";
  10. import {Mat4} from "molstar/lib/mol-math/linear-algebra";
  11. import {BuiltInTrajectoryFormat} from "molstar/lib/mol-plugin-state/formats/trajectory";
  12. import {PluginState} from "molstar/lib/mol-plugin/state";
  13. import {
  14. Structure,
  15. StructureElement,
  16. StructureProperties as SP,
  17. StructureSelection,
  18. Queries as Q,
  19. StructureQuery
  20. } from "molstar/lib/mol-model/structure";
  21. import {OrderedSet} from "molstar/lib/mol-data/int";
  22. import { PluginStateObject as PSO } from 'molstar/lib/mol-plugin-state/objects';
  23. import {State, StateObject} from "molstar/lib/mol-state";
  24. import {StructureComponentRef, StructureRef} from "molstar/lib/mol-plugin-state/manager/structure/hierarchy-state";
  25. import {
  26. RcsbFvSelectorManager
  27. } from "../../RcsbFvSelection/RcsbFvSelectorManager";
  28. import {AbstractPlugin} from "./AbstractPlugin";
  29. import {Subscription} from "rxjs";
  30. import {Script} from "molstar/lib/mol-script/script";
  31. import {MolScriptBuilder as MS} from "molstar/lib/mol-script/language/builder";
  32. import {SetUtils} from "molstar/lib/mol-util/set";
  33. import {StructureRepresentationRegistry} from "molstar/lib/mol-repr/structure/registry";
  34. import {ColorTheme} from "molstar/lib/mol-theme/color";
  35. import {TrajectoryHierarchyPresetProvider} from "molstar/lib/mol-plugin-state/builder/structure/hierarchy-preset";
  36. import {Expression} from "molstar/lib/commonjs/mol-script/language/expression";
  37. export enum LoadMethod {
  38. loadPdbId = "loadPdbId",
  39. loadPdbIds = "loadPdbIds",
  40. loadStructureFromUrl = "loadStructureFromUrl",
  41. loadSnapshotFromUrl = "loadSnapshotFromUrl",
  42. loadStructureFromData = "loadStructureFromData"
  43. }
  44. export interface LoadMolstarInterface {
  45. loadMethod: LoadMethod;
  46. loadParams: LoadParams | Array<LoadParams>;
  47. }
  48. interface LoadParams<P=any,S={}> {
  49. pdbId?: string;
  50. props?: PresetProps;
  51. matrix?: Mat4;
  52. url?: string,
  53. format?: BuiltInTrajectoryFormat,
  54. isBinary?: boolean,
  55. type?: PluginState.SnapshotType,
  56. data?: string | number[]
  57. id?:string;
  58. reprProvider?: TrajectoryHierarchyPresetProvider<P,S>;
  59. params?:P;
  60. }
  61. export class MolstarPlugin extends AbstractPlugin implements SaguaroPluginInterface {
  62. private viewer: Viewer;
  63. private innerSelectionFlag: boolean = false;
  64. private loadingFlag: boolean = false;
  65. private selectCallbackSubs: Subscription;
  66. private modelChangeCallback: (chainMap:SaguaroPluginModelMapType)=>void;
  67. private modelChangeCallbackSubs: Subscription;
  68. private modelMap: Map<string,string|undefined> = new Map<string, string>();
  69. private readonly componentMap: Map<string, StructureComponentRef> = new Map<string, StructureComponentRef>();
  70. constructor(props: RcsbFvSelectorManager) {
  71. super(props);
  72. }
  73. public init(target: string | HTMLElement, props?: Partial<ViewerProps>) {
  74. this.viewer = new Viewer(target, {
  75. ...props,
  76. layoutShowControls:false,
  77. layoutShowSequence: true,
  78. canvas3d: {
  79. multiSample: {
  80. mode: 'off'
  81. }
  82. }
  83. });
  84. }
  85. public clear(): void{
  86. this.viewer.clear();
  87. }
  88. async load(loadConfig: LoadMolstarInterface|Array<LoadMolstarInterface>): Promise<void>{
  89. this.loadingFlag = true;
  90. for (const lC of (Array.isArray(loadConfig) ? loadConfig : [loadConfig])) {
  91. if(MolstarPlugin.checkLoadData(lC)) {
  92. if (lC.loadMethod == LoadMethod.loadPdbId) {
  93. const config: LoadParams = lC.loadParams as LoadParams;
  94. await this.viewer.loadPdbId(config.pdbId!, {props: config.props, matrix: config.matrix, reprProvider: config.reprProvider, params: config.params});
  95. } else if (lC.loadMethod == LoadMethod.loadPdbIds) {
  96. const config: Array<LoadParams> = lC.loadParams as Array<LoadParams>;
  97. await this.viewer.loadPdbIds(config.map((d) => {
  98. return {pdbId: d.pdbId!, config:{props: d.props, matrix: d.matrix, reprProvider: d.reprProvider, params: d.params}}
  99. }));
  100. } else if (lC.loadMethod == LoadMethod.loadStructureFromUrl) {
  101. const config: LoadParams = lC.loadParams as LoadParams;
  102. await this.viewer.loadStructureFromUrl(config.url!, config.format!, config.isBinary!,{props: config.props, matrix: config.matrix, reprProvider: config.reprProvider, params: config.params});
  103. } else if (lC.loadMethod == LoadMethod.loadSnapshotFromUrl) {
  104. const config: LoadParams = lC.loadParams as LoadParams;
  105. await this.viewer.loadSnapshotFromUrl(config.url!, config.type!);
  106. } else if (lC.loadMethod == LoadMethod.loadStructureFromData) {
  107. const config: LoadParams = lC.loadParams as LoadParams;
  108. await this.viewer.loadStructureFromData(config.data!, config.format!, config.isBinary!, {props: config.props, matrix: config.matrix, reprProvider: config.reprProvider, params: config.params});
  109. }
  110. }
  111. this.viewer.plugin.selectionMode = true;
  112. (Array.isArray(lC.loadParams) ? lC.loadParams : [lC.loadParams]).forEach(lP=>{
  113. if(typeof lP.params?.getMap === "function") {
  114. const map: Map<string,string> = lP.params.getMap();
  115. if(typeof map?.forEach === "function")
  116. map.forEach((modelId: string, key: string) => {
  117. if (typeof modelId === "string" && typeof key === "string") {
  118. this.modelMap.set(key, modelId);
  119. this.modelMap.set(modelId, key);
  120. }
  121. })
  122. }
  123. });
  124. this.mapModels(lC.loadParams);
  125. }
  126. this.loadingFlag = false;
  127. this.modelChangeCallback(this.getChains());
  128. }
  129. private static checkLoadData(loadConfig: LoadMolstarInterface): boolean{
  130. const method: LoadMethod = loadConfig.loadMethod;
  131. const params: LoadParams | Array<LoadParams> = loadConfig.loadParams;
  132. if( method == LoadMethod.loadPdbId ){
  133. if(params instanceof Array || params.pdbId == null)
  134. throw loadConfig.loadMethod+": missing pdbId";
  135. }else if( method == LoadMethod.loadPdbIds ){
  136. if(!(params instanceof Array))
  137. throw loadConfig.loadMethod+": Array object spected";
  138. for(const d of params){
  139. if(d.pdbId == null)
  140. throw loadConfig.loadMethod+": missing pdbId"
  141. }
  142. }else if( method == LoadMethod.loadStructureFromUrl ){
  143. if(params instanceof Array || params.url == null || params.isBinary == null || params.format == null)
  144. throw loadConfig.loadMethod+": arguments needed url, format, isBinary"
  145. }else if( method == LoadMethod.loadSnapshotFromUrl ){
  146. if(params instanceof Array || params.url == null || params.type == null)
  147. throw loadConfig.loadMethod+": arguments needed url, type"
  148. }else if( method == LoadMethod.loadStructureFromData ){
  149. if(params instanceof Array || params.data == null || params.format == null || params.isBinary == null)
  150. throw loadConfig.loadMethod+": arguments needed data, format, isBinary"
  151. }
  152. return true;
  153. }
  154. public setBackground(color: number) {
  155. }
  156. public select(modelId:string, labelAsymId: string, begin: number, end: number, mode: 'select'|'hover', operation:'add'|'set', operatorName?:string): void;
  157. public select(selection: Array<SaguaroPosition>, mode: 'select'|'hover', operation:'add'|'set'): void;
  158. public select(selection: Array<SaguaroRange>, mode: 'select'|'hover', operation:'add'|'set'): void;
  159. public select(...args: any[]): void{
  160. if(args.length >= 6){
  161. this.selectRange(args[0],args[1],args[2],args[3],args[4],args[5]);
  162. }else if(args.length === 3 && (args[0] as Array<any>).length > 0 && typeof (args[0] as Array<any>)[0].position === 'number'){
  163. this.selectSet(args[0],args[1],args[2]);
  164. }else if(args.length === 3 && (args[0] as Array<any>).length > 0 && typeof (args[0] as Array<any>)[0].begin === 'number'){
  165. this.selectMultipleRanges(args[0],args[1],args[2]);
  166. }
  167. }
  168. private selectRange(modelId:string, labelAsymId: string, begin: number, end: number, mode: 'select'|'hover', operation:'add'|'set', operatorName?:string): void {
  169. if(mode == null || mode === 'select') {
  170. this.innerSelectionFlag = true;
  171. }
  172. this.viewer.select({modelId:this.getModelId(modelId), labelAsymId: labelAsymId, labelSeqRange:{beg: begin, end:end}, operatorName: operatorName}, mode,operation);
  173. this.innerSelectionFlag = false;
  174. }
  175. private selectSet(selection: Array<SaguaroPosition>, mode: 'select'|'hover', operation:'add'|'set'): void {
  176. if(mode == null || mode === 'select') {
  177. this.innerSelectionFlag = true;
  178. }
  179. this.viewer.select(selection.map(r=>({modelId: this.getModelId(r.modelId), labelSeqId:r.position, labelAsymId: r.labelAsymId, operatorName: r.operatorName})), mode, operation);
  180. this.innerSelectionFlag = false;
  181. }
  182. private selectMultipleRanges(selection: Array<SaguaroRange>, mode: 'select'|'hover', operation:'add'|'set'): void {
  183. if(mode == null || mode === 'select') {
  184. this.innerSelectionFlag = true;
  185. }
  186. this.viewer.select(selection.map(r=>({modelId: this.getModelId(r.modelId), labelAsymId: r.labelAsymId, labelSeqRange:{beg:r.begin, end: r.end}, operatorName: r.operatorName})), mode, operation);
  187. this.innerSelectionFlag = false;
  188. }
  189. public clearSelection(mode:'select'|'hover', option?:SaguaroChain): void {
  190. if(mode === 'select') {
  191. this.viewer.clearFocus();
  192. this.innerSelectionFlag = true;
  193. }
  194. if(option != null)
  195. this.viewer.clearSelection(mode, {...option, modelId: this.getModelId(option.modelId)});
  196. else
  197. this.viewer.clearSelection(mode);
  198. this.innerSelectionFlag = false;
  199. }
  200. public setFocus(modelId: string, labelAsymId: string, begin: number, end: number, operatorName?:string): void{
  201. this.viewer.setFocus({modelId: this.getModelId(modelId), labelAsymId: labelAsymId, labelSeqRange:{beg:begin, end: end}, operatorName: operatorName});
  202. }
  203. public clearFocus(): void {
  204. this.viewer.clearFocus();
  205. }
  206. public cameraFocus(modelId: string, labelAsymId: string, positions:Array<number>, operatorName?:string): void;
  207. public cameraFocus(modelId: string, labelAsymId: string, begin: number, end: number, operatorName?:string): void;
  208. public cameraFocus(...args: any[]): void{
  209. if(typeof args[3] === "number"){
  210. this.focusRange(args[0],args[1],args[2],args[3],args[4]);
  211. }else{
  212. this.focusPositions(args[0],args[1],args[2],args[3]);
  213. }
  214. }
  215. private focusPositions(modelId: string, labelAsymId: string, positions:Array<number>, operatorName?:string): void{
  216. const structure: Structure | undefined = getStructureWithModelId(this.viewer.plugin.managers.structure.hierarchy.current.structures, this.getModelId(modelId));
  217. if (structure == null) return;
  218. const chainTests: Expression[] = [MS.core.rel.eq([MS.ammp('label_asym_id'), labelAsymId])];
  219. if(operatorName)
  220. chainTests.push(MS.core.rel.eq([operatorName, MS.acp('operatorName')]));
  221. const sel: StructureSelection = Script.getStructureSelection(Q => Q.struct.generator.atomGroups({
  222. 'chain-test': Q.core.logic.and(chainTests),
  223. 'residue-test': Q.core.set.has([MS.set(...SetUtils.toArray(new Set(positions))), MS.ammp('label_seq_id')])
  224. }), structure);
  225. const loci: Loci = StructureSelection.toLociWithSourceUnits(sel);
  226. if(!StructureElement.Loci.isEmpty(loci))
  227. this.viewer.plugin.managers.camera.focusLoci(loci);
  228. else
  229. this.viewer.plugin.managers.camera.reset();
  230. }
  231. private focusRange(modelId: string, labelAsymId: string, begin: number, end: number, operatorName?:string): void{
  232. const seqIds: Array<number> = new Array<number>();
  233. for(let n = begin; n <= end; n++){
  234. seqIds.push(n);
  235. }
  236. this.focusPositions(modelId, labelAsymId, seqIds, operatorName);
  237. }
  238. public async createComponent(componentLabel: string, modelId:string, labelAsymId: string, begin: number, end : number, representationType: StructureRepresentationRegistry.BuiltIn, operatorName?:string): Promise<void>;
  239. public async createComponent(componentLabel: string, modelId:string, labelAsymId: string, representationType: StructureRepresentationRegistry.BuiltIn, operatorName?:string): Promise<void>;
  240. public async createComponent(componentLabel: string, residues: Array<SaguaroPosition>, representationType: StructureRepresentationRegistry.BuiltIn): Promise<void>;
  241. public async createComponent(componentLabel: string, residues: Array<SaguaroRange>, representationType: StructureRepresentationRegistry.BuiltIn): Promise<void>;
  242. public async createComponent(...args: any[]): Promise<void> {
  243. this.removeComponent(args[0]);
  244. if(args.length === 3){
  245. if( args[1] instanceof Array && args[1].length > 0 ) {
  246. if(typeof args[1][0].position === "number"){
  247. await this.viewer.createComponent(args[0], args[1].map(r=>({modelId: this.getModelId(r.modelId), labelAsymId: r.labelAsymId, labelSeqId: r.position, operatorName: r.operatorName})), args[2]);
  248. }else{
  249. await this.viewer.createComponent(args[0], args[1].map(r=>({modelId: this.getModelId(r.modelId), labelAsymId: r.labelAsymId, labelSeqRange:{beg:r.begin, end: r.end}, operatorName: r.operatorName})), args[2]);
  250. }
  251. }
  252. }else if(args.length >= 6){
  253. await this.viewer.createComponent(args[0], {modelId: this.getModelId(args[1]), labelAsymId: args[2], labelSeqRange:{beg:args[3], end:args[4]}, operatorName: args[6]}, args[5]);
  254. }else{
  255. await this.viewer.createComponent(args[0], {modelId: this.getModelId(args[1]), labelAsymId:args[2], operatorName: args[4]}, args[3]);
  256. }
  257. this.componentMap.set(args[0], this.viewer.plugin.managers.structure.hierarchy.currentComponentGroups[this.viewer.plugin.managers.structure.hierarchy.currentComponentGroups.length-1][0]);
  258. }
  259. public isComponent(componentLabel: string): boolean{
  260. for(const c of this.viewer.plugin.managers.structure.hierarchy.currentComponentGroups){
  261. for(const comp of c){
  262. if(comp.cell.obj?.label === componentLabel) {
  263. return true;
  264. }
  265. }
  266. }
  267. return false;
  268. }
  269. public async colorComponent(componentLabel: string, color: ColorTheme.BuiltIn): Promise<void>{
  270. for(const c of this.viewer.plugin.managers.structure.hierarchy.currentComponentGroups){
  271. for(const comp of c){
  272. if(comp.cell.obj?.label === componentLabel) {
  273. await this.viewer.plugin.managers.structure.component.updateRepresentationsTheme([comp], { color: color });
  274. return;
  275. }
  276. }
  277. }
  278. }
  279. public getComponentSet(): Set<string>{
  280. const out: Set<string> = new Set<string>();
  281. this.viewer.plugin.managers.structure.hierarchy.currentComponentGroups.forEach((c)=>{
  282. for(const comp of c){
  283. if(comp.cell.obj?.label != null && out.has(comp.cell.obj?.label)) {
  284. break;
  285. }else if(comp.cell.obj?.label != null){
  286. out.add(comp.cell.obj?.label);
  287. }
  288. }
  289. });
  290. return out;
  291. }
  292. public removeComponent(componentLabel?: string): void{
  293. if(componentLabel == null){
  294. this.componentMap.forEach((comp, id)=>{
  295. this.viewer.removeComponent(id);
  296. this.componentMap.delete(id);
  297. })
  298. }else{
  299. this.viewer.removeComponent(componentLabel);
  300. this.componentMap.delete(componentLabel);
  301. }
  302. }
  303. public displayComponent(componentLabel: string): boolean;
  304. public displayComponent(componentLabel: string, visibilityFlag: boolean): void;
  305. public displayComponent(componentLabel: string, visibilityFlag?: boolean): void|boolean {
  306. if(typeof visibilityFlag === 'boolean')
  307. return this.changeComponentDisplay(componentLabel, visibilityFlag);
  308. else
  309. return this.getComponentDisplay(componentLabel);
  310. }
  311. private changeComponentDisplay(componentLabel: string, visibilityFlag: boolean): void{
  312. if(this.componentMap.has(componentLabel) && this.getComponentDisplay(componentLabel) != visibilityFlag) {
  313. this.viewer.plugin.managers.structure.component.toggleVisibility([this.componentMap.get(componentLabel)!]);
  314. }else if(!this.componentMap.has(componentLabel)){
  315. for (const c of this.viewer.plugin.managers.structure.hierarchy.currentComponentGroups) {
  316. for (const comp of c) {
  317. if(comp.cell.obj?.label === componentLabel) {
  318. if(!comp.cell.state.isHidden != visibilityFlag) {
  319. this.viewer.plugin.managers.structure.component.toggleVisibility(c);
  320. return void 0;
  321. }
  322. }
  323. }
  324. }
  325. }
  326. }
  327. private getComponentDisplay(componentLabel: string): boolean | undefined{
  328. if(this.componentMap.has(componentLabel)) {
  329. return !this.componentMap.get(componentLabel)!.cell.state.isHidden!;
  330. }
  331. }
  332. public setRepresentationChangeCallback(g:()=>void){
  333. }
  334. public setHoverCallback(g:()=>void){
  335. this.viewer.plugin.behaviors.interaction.hover.subscribe((r)=>{
  336. const sequenceData: Array<SaguaroSet> = new Array<SaguaroSet>();
  337. const loci:Loci = r.current.loci;
  338. if(StructureElement.Loci.is(loci)){
  339. const loc = StructureElement.Location.create(loci.structure);
  340. for (const e of loci.elements) {
  341. const modelId: string = e.unit?.model?.id;
  342. const seqIds = new Set<number>();
  343. loc.unit = e.unit;
  344. for (let i = 0, il = OrderedSet.size(e.indices); i < il; ++i) {
  345. loc.element = e.unit.elements[OrderedSet.getAt(e.indices, i)];
  346. seqIds.add(SP.residue.label_seq_id(loc));
  347. }
  348. sequenceData.push({
  349. modelId: this.getModelId(modelId),
  350. labelAsymId: SP.chain.label_asym_id(loc),
  351. operatorName: SP.unit.operator_name(loc),
  352. seqIds
  353. });
  354. }
  355. }
  356. this.selection.setSelectionFromResidueSelection(sequenceData, 'hover', 'structure');
  357. g();
  358. });
  359. }
  360. public setSelectCallback(g:(flag?:boolean)=>void){
  361. this.selectCallbackSubs = this.viewer.plugin.managers.structure.selection.events.changed.subscribe(()=>{
  362. if(this.innerSelectionFlag) {
  363. return;
  364. }
  365. if(this.viewer.plugin.managers.structure.selection.additionsHistory.length > 0) {
  366. const currentLoci: Loci = this.viewer.plugin.managers.structure.selection.additionsHistory[0].loci;
  367. const loc: StructureElement.Location = StructureElement.Location.create(currentLoci.structure);
  368. StructureElement.Location.set(
  369. loc,
  370. currentLoci.structure,
  371. currentLoci.elements[0].unit,
  372. currentLoci.elements[0].unit.elements[OrderedSet.getAt(currentLoci.elements[0].indices,0)]
  373. );
  374. const currentModelId: string = this.getModelId(currentLoci.structure.model.id);
  375. if(currentLoci.elements.length > 0)
  376. if(SP.entity.type(loc) === 'non-polymer') {
  377. const resAuthId: number = SP.residue.auth_seq_id(loc);
  378. const chainLabelId: string = SP.chain.label_asym_id(loc);
  379. const query: StructureQuery = Q.modifiers.includeSurroundings(
  380. Q.generators.residues({
  381. residueTest:l=>SP.residue.auth_seq_id(l.element) === resAuthId,
  382. chainTest:l=>SP.chain.label_asym_id(l.element) === chainLabelId
  383. }),
  384. {
  385. radius: 5,
  386. wholeResidues: true
  387. });
  388. this.innerSelectionFlag = true;
  389. const sel: StructureSelection = StructureQuery.run(query, currentLoci.structure);
  390. const surroundingsLoci: Loci = StructureSelection.toLociWithSourceUnits(sel);
  391. this.viewer.plugin.managers.structure.selection.fromLoci('add', surroundingsLoci);
  392. const surroundingsLoc = StructureElement.Location.create(surroundingsLoci.structure);
  393. for (const e of surroundingsLoci.elements) {
  394. StructureElement.Location.set(surroundingsLoc, surroundingsLoci.structure, e.unit, e.unit.elements[0]);
  395. if(SP.entity.type(surroundingsLoc) === 'polymer'){
  396. this.selection.setLastSelection('select', {
  397. modelId: currentModelId,
  398. labelAsymId: SP.chain.label_asym_id(surroundingsLoc),
  399. regions: []
  400. });
  401. }
  402. }
  403. this.innerSelectionFlag = false;
  404. }else if( SP.entity.type(loc) === 'polymer' ) {
  405. this.selection.setLastSelection('select', {
  406. modelId: currentModelId,
  407. labelAsymId: SP.chain.label_asym_id(loc),
  408. operatorName: SP.unit.operator_name(loc),
  409. regions: []
  410. });
  411. }else{
  412. this.selection.setLastSelection('select', null);
  413. }
  414. }else{
  415. this.selection.setLastSelection('select', null);
  416. }
  417. const sequenceData: Array<SaguaroSet> = new Array<SaguaroSet>();
  418. for(const structure of this.viewer.plugin.managers.structure.hierarchy.current.structures){
  419. const data: Structure | undefined = structure.cell.obj?.data;
  420. if(data == null) return;
  421. const loci: Loci = this.viewer.plugin.managers.structure.selection.getLoci(data);
  422. if(StructureElement.Loci.is(loci)){
  423. const loc = StructureElement.Location.create(loci.structure);
  424. for (const e of loci.elements) {
  425. StructureElement.Location.set(loc, loci.structure, e.unit, e.unit.elements[0]);
  426. const seqIds = new Set<number>();
  427. for (let i = 0, il = OrderedSet.size(e.indices); i < il; ++i) {
  428. loc.element = e.unit.elements[OrderedSet.getAt(e.indices, i)];
  429. seqIds.add(SP.residue.label_seq_id(loc));
  430. }
  431. sequenceData.push({
  432. modelId: this.getModelId(data.model.id),
  433. labelAsymId: SP.chain.label_asym_id(loc),
  434. operatorName: SP.unit.operator_name(loc),
  435. seqIds
  436. });
  437. }
  438. }
  439. }
  440. this.selection.setSelectionFromResidueSelection(sequenceData, 'select', 'structure');
  441. g();
  442. });
  443. }
  444. public pluginCall(f: (plugin: PluginContext) => void){
  445. this.viewer.pluginCall(f);
  446. }
  447. public setModelChangeCallback(f:(modelMap:SaguaroPluginModelMapType)=>void){
  448. this.modelChangeCallback = f;
  449. this.modelChangeCallbackSubs = this.viewer.plugin.state.events.object.updated.subscribe((o:{obj: StateObject, action: "in-place" | "recreate"})=>{
  450. if(this.loadingFlag)
  451. return;
  452. if(o.obj.type.name === "Behavior" && o.action === "in-place") {
  453. f(this.getChains());
  454. }else if(o.obj.type.name === "Model" && o.action === "in-place"){
  455. f(this.getChains());
  456. }
  457. });
  458. }
  459. private getChains(): SaguaroPluginModelMapType{
  460. const structureRefList = getStructureOptions(this.viewer.plugin);
  461. const out: SaguaroPluginModelMapType = new Map<string, {entryId: string; chains: Array<ChainInfo>; assemblyId:string;}>();
  462. structureRefList.forEach((structureRef,i)=>{
  463. const structure: Structure = getStructure(structureRef[0], this.viewer.plugin.state.data);
  464. let modelEntityId = getModelEntityOptions(structure)[0][0];
  465. const chains: [{modelId:string;entryId:string;assemblyId:string;},ChainInfo[]] = getChainValues(structure, modelEntityId);
  466. out.set(this.getModelId(chains[0].modelId),{entryId:chains[0].entryId, assemblyId:chains[0].assemblyId, chains: chains[1]});
  467. });
  468. return out;
  469. }
  470. private mapModels(loadParams: LoadParams | Array<LoadParams>): void{
  471. const loadParamList: Array<LoadParams> = loadParams instanceof Array ? loadParams : [loadParams];
  472. const structureRefList = getStructureOptions(this.viewer.plugin);
  473. if(loadParamList.length == structureRefList.length )
  474. structureRefList.forEach((structureRef,i)=>{
  475. const structure = getStructure(structureRef[0], this.viewer.plugin.state.data);
  476. let modelEntityId = getModelEntityOptions(structure)[0][0];
  477. const chains: [{modelId:string, entryId:string},ChainInfo[]] = getChainValues(structure, modelEntityId);
  478. if(!this.modelMap.has(chains[0].modelId)) {
  479. this.modelMap.set(chains[0].modelId, loadParamList[i].id);
  480. if (loadParamList[i].id != null)
  481. this.modelMap.set(loadParamList[i].id!, chains[0].modelId);
  482. }
  483. });
  484. }
  485. private getModelId(id: string): string{
  486. return this.modelMap.get(id) ?? id;
  487. }
  488. public unsetCallbacks(): void {
  489. this.selectCallbackSubs?.unsubscribe();
  490. this.modelChangeCallbackSubs?.unsubscribe();
  491. }
  492. public resetCamera(): void {
  493. this.viewer.plugin.managers.camera.reset();
  494. }
  495. }
  496. function getStructureOptions(plugin: PluginContext): [string,string][] {
  497. const options: [string, string][] = [];
  498. plugin.managers.structure.hierarchy.current.structures.forEach(s=>{
  499. options.push([s.cell.transform.ref, s.cell.obj!.data.label]);
  500. })
  501. return options;
  502. }
  503. function getChainValues(structure: Structure, modelEntityId: string): [{modelId:string; entryId:string; assemblyId:string;},ChainInfo[]] {
  504. const chains: Map<number, ChainInfo> = new Map<number, ChainInfo>();
  505. const l = StructureElement.Location.create(structure);
  506. let assemblyId:string = "-";
  507. const [modelIdx, entityId] = splitModelEntityId(modelEntityId);
  508. for (const unit of structure.units) {
  509. StructureElement.Location.set(l, structure, unit, unit.elements[0]);
  510. assemblyId = SP.unit.pdbx_struct_assembly_id(l);
  511. if (structure.getModelIndex(unit.model) !== modelIdx) continue;
  512. const chId: number = unit.chainGroupId;
  513. if(chains.has(chId)){
  514. chains.get(chId)!.operators.push(opKey(l))
  515. }else{
  516. chains.set(chId, {label:SP.chain.label_asym_id(l), auth:SP.chain.auth_asym_id(l), entityId: SP.entity.id(l), title: SP.entity.pdbx_description(l).join("|"), type: SP.entity.type(l), operators:[opKey(l)]});
  517. }
  518. }
  519. const id: {modelId:string; entryId:string; assemblyId:string;} = {modelId:l.unit?.model?.id, entryId: l.unit?.model?.entryId, assemblyId: assemblyId};
  520. return [id,Array.from(chains.values())];
  521. }
  522. function getStructureWithModelId(structures: StructureRef[], modelId: string): Structure|undefined{
  523. for(const structure of structures){
  524. if(!structure.cell?.obj?.data?.units)
  525. continue;
  526. const unit = structure.cell.obj.data.units[0];
  527. const id:string = unit.model.id;
  528. if(id === modelId)
  529. return structure.cell.obj.data
  530. }
  531. }
  532. function getStructure(ref: string, state: State) {
  533. const cell = state.select(ref)[0];
  534. if (!ref || !cell || !cell.obj) return Structure.Empty;
  535. return (cell.obj as PSO.Molecule.Structure).data;
  536. }
  537. function getModelEntityOptions(structure: Structure):[string, string][] {
  538. const options: [string, string][] = [];
  539. const l = StructureElement.Location.create(structure);
  540. const seen = new Set<string>();
  541. for (const unit of structure.units) {
  542. StructureElement.Location.set(l, structure, unit, unit.elements[0]);
  543. const id = SP.entity.id(l);
  544. const modelIdx = structure.getModelIndex(unit.model);
  545. const key = `${modelIdx}|${id}`;
  546. if (seen.has(key)) continue;
  547. let description = SP.entity.pdbx_description(l).join(', ');
  548. if (structure.models.length) {
  549. if (structure.representativeModel) { // indicates model trajectory
  550. description += ` (Model ${structure.models[modelIdx].modelNum})`;
  551. } else if (description.startsWith('Polymer ')) { // indicates generic entity name
  552. description += ` (${structure.models[modelIdx].entry})`;
  553. }
  554. }
  555. const label = `${id}: ${description}`;
  556. options.push([ key, label ]);
  557. seen.add(key);
  558. }
  559. if (options.length === 0) options.push(['', 'No entities']);
  560. return options;
  561. }
  562. function splitModelEntityId(modelEntityId: string) {
  563. const [ modelIdx, entityId ] = modelEntityId.split('|');
  564. return [ parseInt(modelIdx), entityId ];
  565. }
  566. function opKey(l: StructureElement.Location): OperatorInfo {
  567. const ids = SP.unit.pdbx_struct_oper_list_ids(l);
  568. const ncs = SP.unit.struct_ncs_oper_id(l);
  569. const hkl = SP.unit.hkl(l);
  570. const spgrOp = SP.unit.spgrOp(l);
  571. const name = SP.unit.operator_name(l);
  572. return {ids:ids,name:name};
  573. }