Skip to content
Snippets Groups Projects
Commit f740ba95 authored by Michal Malý's avatar Michal Malý
Browse files

ReDNATCO plugin stage 5

parent c9d8235a
No related branches found
No related tags found
No related merge requests found
export namespace Commands {
export type Type = 'select-step'|'switch-model';
export type SelectStep = {
type: 'select-step';
stepName: string;
referenceNtC: string;
references: ('sel'|'prev'|'next')[];
}
export function SelectStep(stepName: string, referenceNtC = '', references = ['sel', 'prev', 'next']): SelectStep {
return {
type: 'select-step',
stepName,
referenceNtC,
references: references as ('sel'|'prev'|'next')[],
};
}
export type SwitchModel = { type: 'switch-model', model: number };
export function SwitchModel(model: number): SwitchModel { return { type: 'switch-model', model }; }
export type Cmd = SelectStep|SwitchModel;
}
......@@ -9,7 +9,7 @@
<script type="text/javascript" src="./molstar.js"></script>
<script>
async function loadStructure() {
const resp = await fetch('./3vok_v32C35A23.cif');
const resp = await fetch('./1bna_v41C35A23.cif');
const data = await resp.text();
molstar.ReDNATCOMspApi.loadStructure(data);
......
......@@ -2,18 +2,20 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { NtCColors } from './colors';
import { ColorPicker } from './color-picker';
import { Commands } from './commands';
import { PushButton, ToggleButton } from './controls';
import * as IDs from './idents';
import * as RefCfmr from './reference-conformers';
import { ReferenceConformersPdbs } from './reference-conformers-pdbs';
import { Step } from './step';
import { Superpose } from './superpose';
import { Traverse } from './traverse';
import { DnatcoConfalPyramids } from '../../extensions/dnatco';
import { ConfalPyramidsParams } from '../../extensions/dnatco/confal-pyramids/representation';
import { OrderedSet } from '../../mol-data/int/ordered-set';
import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
import { Loci } from '../../mol-model/loci';
import { Model, Structure, StructureElement, StructureProperties, Trajectory } from '../../mol-model/structure';
import { Model, Structure, StructureElement, StructureProperties, StructureSelection, Trajectory } from '../../mol-model/structure';
import { Location } from '../../mol-model/structure/structure/element/location';
import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
import { PluginBehavior, PluginBehaviors } from '../../mol-plugin/behavior';
......@@ -30,7 +32,6 @@ import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
import { Representation } from '../../mol-repr/representation';
import { StateObjectCell, StateObject, StateSelection } from '../../mol-state';
import { StateTreeSpine } from '../../mol-state/tree/spine';
import { Script } from '../../mol-script/script';
import { lociLabel } from '../../mol-theme/label';
import { Color } from '../../mol-util/color';
import { arrayMax } from '../../mol-util/array';
......@@ -54,7 +55,6 @@ const NtCSupPrev = 'ntc-sup-prev';
const NtCSupSel = 'ntc-sup-sel';
const NtCSupNext = 'ntc-sup-next';
const SelectAllScript = Script('(sel.atom.atoms true)', 'mol-script');
const SphereBoundaryHelper = new BoundaryHelper('98');
type StepInfo = {
......@@ -171,8 +171,9 @@ const ConformersByClass = {
};
type ConformersByClass = typeof ConformersByClass;
type VisualRepresentations = 'ball-and-stick'|'cartoon';
const Display = {
representation: 'cartoon',
representation: 'cartoon' as VisualRepresentations,
showNucleic: true,
showProtein: false,
......@@ -350,6 +351,13 @@ class ReDNATCOMspViewer {
interactionContext.self = this;
}
private currentModelNumber() {
const model = this.plugin.state.data.cells.get(IDs.ID('model', '', BaseRef))?.obj;
if (!model)
return -1;
return (model as StateObject<Model>).data.modelNum;
}
private getBuilder(id: IDs.ID, sub: IDs.Substructure|'' = '', ref = BaseRef) {
return this.plugin.state.data.build().to(IDs.ID(id, sub, ref));
}
......@@ -401,7 +409,8 @@ class ReDNATCOMspViewer {
continue;
const parent = this.getStructureParent(cell);
if (parent) {
const s = Loci.getBoundingSphere(Script.toLoci(SelectAllScript, parent.data));
const loci = StructureSelection.toLociWithSourceUnits(StructureSelection.Singletons(parent.data, parent.data));
const s = Loci.getBoundingSphere(loci);
if (s)
spheres.push(s);
}
......@@ -424,6 +433,27 @@ class ReDNATCOMspViewer {
PluginCommands.Camera.SetSnapshot(this.plugin, { snapshot, durationMs: AnimationDurationMsec });
}
private substructureVisuals(representation: 'ball-and-stick'|'cartoon') {
switch (representation) {
case 'cartoon':
return {
type: {
name: 'cartoon',
params: { sizeFactor: 0.2, sizeAspectRatio: 0.35, aromaticBonds: false },
},
colorTheme: { name: 'chain-id', params: { asymId: 'auth' } },
};
case 'ball-and-stick':
return {
type: {
name: 'ball-and-stick',
params: { sizeFactor: 0.2, sizeAspectRatio: 0.35, aromaticBonds: false },
},
colorTheme: { name: 'element-symbol', params: { carbonColor: 'chain-id' } },
};
}
}
private superpose(reference: StructureElement.Loci, stru: StructureElement.Loci) {
const refElems = dinucleotideBackbone(reference);
const struElems = dinucleotideBackbone(stru);
......@@ -525,9 +555,8 @@ class ReDNATCOMspViewer {
);
await b.commit();
}
} else {
} else
await PluginCommands.State.RemoveObject(this.plugin, { state: this.plugin.state.data, ref: IDs.ID('pyramids', 'nucleic', BaseRef) });
}
}
async changeRepresentation(display: Partial<Display>) {
......@@ -541,7 +570,7 @@ class ReDNATCOMspViewer {
StateTransforms.Representation.StructureRepresentation3D,
old => ({
...old,
type: { ...old.type, name: repr }
...this.substructureVisuals(repr),
})
);
}
......@@ -689,9 +718,7 @@ class ReDNATCOMspViewer {
bb.to(IDs.ID('structure', 'nucleic', BaseRef))
.apply(
StateTransforms.Representation.StructureRepresentation3D,
{
type: { name: display.representation ?? 'cartoon', params: { sizeFactor: 0.2, sizeAspectRatio: 0.35, aromaticBonds: false } },
},
this.substructureVisuals('cartoon'),
{ ref: IDs.ID('visual', 'nucleic', BaseRef) }
);
if (display.showPyramids) {
......@@ -707,9 +734,7 @@ class ReDNATCOMspViewer {
bb.to(IDs.ID('structure', 'protein', BaseRef))
.apply(
StateTransforms.Representation.StructureRepresentation3D,
{
type: { name: display.representation ?? 'cartoon', params: { sizeFactor: 0.2, sizeAspectRatio: 0.35, aromaticBonds: false } },
},
this.substructureVisuals('cartoon'),
{ ref: IDs.ID('visual', 'protein', BaseRef) }
);
}
......@@ -717,9 +742,7 @@ class ReDNATCOMspViewer {
bb.to(IDs.ID('structure', 'water', BaseRef))
.apply(
StateTransforms.Representation.StructureRepresentation3D,
{
type: { name: display.representation ?? 'ball-and-stick', params: { sizeFactor: 0.2, sizeAspectRatio: 0.35 } },
},
this.substructureVisuals('ball-and-stick'),
{ ref: IDs.ID('visual', 'water', BaseRef) }
);
}
......@@ -770,35 +793,81 @@ class ReDNATCOMspViewer {
onLociSelected(selected: Representation.Loci) {
const loci = Loci.normalize(selected.loci, 'two-residues');
if (loci.kind === 'element-loci')
this.superposeReferences(loci);
if (loci.kind === 'element-loci') {
// TODO: This cannot call superposeReferences directly
// Instead, we must make a callback via the API
// and have the listener decide what to do with this event
const stepDesc = Step.describe(loci);
if (!stepDesc)
return;
const stepName = Step.name(stepDesc, this.haveMultipleModels);
this.superposeReferences(stepName, '', []);
}
}
async switchModel(display: Partial<Display>) {
const b = this.getBuilder('model', '', BaseRef);
b.update(
StateTransforms.Model.ModelFromTrajectory,
old => ({
...old,
modelIndex: display.modelNumber ? display.modelNumber - 1 : 0
})
);
if (display.modelNumber && display.modelNumber === this.currentModelNumber())
return;
const b = this.plugin.state.data.build()
.delete(IDs.ID('superposition', '', NtCSupSel))
.delete(IDs.ID('superposition', '', NtCSupPrev))
.delete(IDs.ID('superposition', '', NtCSupNext))
.to(IDs.ID('model', '', BaseRef))
.update(
StateTransforms.Model.ModelFromTrajectory,
old => ({
...old,
modelIndex: display.modelNumber ? display.modelNumber - 1 : 0
})
);
await b.commit();
}
superposeReferences(selected: StructureElement.Loci) {
const step = Step.describe(selected);
if (!step)
return;
async superposeReferences(stepName: string, referenceNtc: string, references: ('sel'|'prev'|'next')[]) {
const ReferenceVisuals = (color: number) => {
return {
type: { name: 'ball-and-stick', params: { sizeFactor: 0.15, aromaticBonds: false } },
colorTheme: { name: 'uniform', params: { value: Color(color) } },
};
};
const stepName = Step.name(step, this.haveMultipleModels);
const stepDesc = Step.fromName(stepName);
if (!stepDesc)
return;
const stepId = this.stepNames.get(stepName);
if (stepId === undefined) {
console.error(`Unknown step name ${stepName}`);
return;
}
if (stepDesc.model !== this.currentModelNumber()) {
const b = this.getBuilder('model')
.update(
StateTransforms.Model.ModelFromTrajectory,
old => ({
...old,
modelIndex: stepDesc.model - 1,
})
);
await b.commit();
}
const entireStru = this.plugin.state.data.cells.get(IDs.ID('structure', 'nucleic', BaseRef))!.obj!;
const loci = Traverse.findResidue(
stepDesc.chain,
stepDesc.resNo1,
stepDesc.altId1,
StructureSelection.toLociWithSourceUnits(StructureSelection.Singletons(entireStru.data, entireStru.data)),
'auth'
);
if (loci.kind !== 'element-loci')
return;
const selLoci = Loci.normalize(loci, 'two-residues');
if (selLoci.kind !== 'element-loci')
return;
const stepIdPrev = stepId === 0 ? void 0 : stepId - 1;
const stepIdNext = stepId === this.steps.length - 1 ? void 0 : stepId + 1;
......@@ -807,7 +876,7 @@ class ReDNATCOMspViewer {
const ntcRefNext = this.ntcRef(stepIdNext, 'next');
if (!ntcRefSel) {
console.error(`Seemingly invalid stepId ${stepId}`);
console.error(`stepId ${stepId} does not map to a known step`);
return;
}
......@@ -816,74 +885,39 @@ class ReDNATCOMspViewer {
.delete(IDs.ID('superposition', '', NtCSupPrev))
.delete(IDs.ID('superposition', '', NtCSupNext));
{
const stru = this.plugin.state.data.cells.get(IDs.ID('structure', '', ntcRefSel))!.obj!;
const loci = Script.toLoci(SelectAllScript, stru.data);
const addReference = (ntcRef: string, superposRef: string, loci: Loci, color: number) => {
const refStru = this.plugin.state.data.cells.get(IDs.ID('structure', '', ntcRef))!.obj!;
const refLoci = StructureSelection.toLociWithSourceUnits(StructureSelection.Singletons(refStru.data, refStru.data));
const { bTransform } = this.superpose(loci, selected);
if (isNaN(bTransform[0])) {
console.error(`Cannot superpose reference conformer ${ntcRefSel} onto selection`);
return;
}
b.to(IDs.ID('structure', '', ntcRefSel))
.apply(
StateTransforms.Model.TransformStructureConformation,
{ transform: { name: 'matrix', params: { data: bTransform, transpose: false } } },
{ ref: IDs.ID('superposition', '', NtCSupSel) }
).apply(
StateTransforms.Representation.StructureRepresentation3D,
{ type: { name: 'ball-and-stick', params: { sizeFactor: 0.15 } } },
{ ref: IDs.ID('visual', '', NtCSupSel) }
);
}
// TODO: These cannot be applied onto selection!!!
if (ntcRefPrev) {
const stru = this.plugin.state.data.cells.get(IDs.ID('structure', '', ntcRefPrev))!.obj!;
const loci = Script.toLoci(SelectAllScript, stru.data);
const { bTransform } = this.superpose(loci, selected);
if (isNaN(bTransform[0])) {
console.error(`Cannot superpose reference conformer ${ntcRefPrev} onto selection`);
return;
}
b.to(IDs.ID('structure', '', ntcRefPrev))
.apply(
StateTransforms.Model.TransformStructureConformation,
{ transform: { name: 'matrix', params: { data: bTransform, transpose: false } } },
{ ref: IDs.ID('superposition', '', NtCSupPrev) }
).apply(
StateTransforms.Representation.StructureRepresentation3D,
{ type: { name: 'ball-and-stick', params: { sizeFactor: 0.15 } } },
{ ref: IDs.ID('visual', '', NtCSupPrev) }
);
}
// TODO: These cannot be applied onto selection!!!
if (ntcRefNext) {
const stru = this.plugin.state.data.cells.get(IDs.ID('structure', '', ntcRefNext))!.obj!;
const loci = Script.toLoci(SelectAllScript, stru.data);
const { bTransform } = this.superpose(loci, selected);
if (isNaN(bTransform[0])) {
console.error(`Cannot superpose reference conformer ${ntcRefNext} onto selection`);
return;
if (loci.kind === 'element-loci' && Step.is(loci)) {
const { bTransform, rmsd } = this.superpose(refLoci, loci);
if (isNaN(bTransform[0])) {
console.error(`Cannot superpose reference conformer ${ntcRef} onto selection`);
return;
}
b.to(IDs.ID('structure', '', ntcRef))
.apply(
StateTransforms.Model.TransformStructureConformation,
{ transform: { name: 'matrix', params: { data: bTransform, transpose: false } } },
{ ref: IDs.ID('superposition', '', superposRef) }
).apply(
StateTransforms.Representation.StructureRepresentation3D,
ReferenceVisuals(color),
{ ref: IDs.ID('visual', '', superposRef) }
);
return rmsd;
}
b.to(IDs.ID('structure', '', ntcRefNext))
.apply(
StateTransforms.Model.TransformStructureConformation,
{ transform: { name: 'matrix', params: { data: bTransform, transpose: false } } },
{ ref: IDs.ID('superposition', '', NtCSupNext) }
).apply(
StateTransforms.Representation.StructureRepresentation3D,
{ type: { name: 'ball-and-stick', params: { sizeFactor: 0.15 } } },
{ ref: IDs.ID('visual', '', NtCSupNext) }
);
}
};
// TODO: Superpose previous and next step too!!!
const rmsd = addReference(ntcRefSel, NtCSupSel, selLoci, 0x008000);
if (ntcRefPrev)
addReference(ntcRefPrev, NtCSupPrev, Loci.normalize(Traverse.residue(-1, stepDesc.altId1, selLoci), 'two-residues'), 0x0000FF);
if (ntcRefNext)
addReference(ntcRefNext, NtCSupNext, Loci.normalize(Traverse.residue(1, stepDesc.altId2, selLoci), 'two-residues'), 0x00FFFF);
b.commit();
return rmsd;
}
async toggleSubstructure(sub: IDs.Substructure, display: Partial<Display>) {
......@@ -893,12 +927,11 @@ class ReDNATCOMspViewer {
if (show) {
const b = this.getBuilder('structure', sub);
const visuals = this.substructureVisuals(sub === 'water' ? 'ball-and-stick' : repr);
if (b) {
b.apply(
StateTransforms.Representation.StructureRepresentation3D,
{
type: { name: repr, params: { sizeFactor: 0.2, sizeAspectRatio: 0.35, aromaticBonds: false } }, // TODO: Use different params for water
},
visuals,
{ ref: IDs.ID('visual', sub, BaseRef) }
);
await b.commit();
......@@ -949,6 +982,26 @@ class ReDNATCOMsp extends React.Component<ReDNATCOMsp.Props, State> {
this.setState({ ...this.state, display });
}
command(cmd: Commands.Cmd) {
if (!this.viewer)
return;
if (cmd.type === 'select-step') {
this.viewer.superposeReferences(cmd.stepName, cmd.referenceNtC, cmd.references);
} else if (cmd.type === 'switch-model') {
if (cmd.model < 1 || cmd.model > this.viewer.getModelCount())
return;
const display: Display = {
...this.state.display,
modelNumber: cmd.model,
};
this.viewer.switchModel(display);
this.setState({ ...this.state, display });
}
}
constructor(props: ReDNATCOMsp.Props) {
super(props);
......@@ -972,7 +1025,7 @@ class ReDNATCOMsp extends React.Component<ReDNATCOMsp.Props, State> {
ReDNATCOMspViewer.create(elem!).then(viewer => {
this.viewer = viewer;
this.viewer.loadReferenceConformers().then(() => {
ReDNATCOMspApi.bind(this);
ReDNATCOMspApi._bind(this);
if (this.props.onInited)
this.props.onInited();
......@@ -1006,7 +1059,7 @@ class ReDNATCOMsp extends React.Component<ReDNATCOMsp.Props, State> {
text={capitalize(this.state.display.representation)}
enabled={ready}
onClick={() => {
const display = {
const display: Display = {
...this.state.display,
representation: this.state.display.representation === 'cartoon' ? 'ball-and-stick' : 'cartoon',
};
......@@ -1025,7 +1078,7 @@ class ReDNATCOMsp extends React.Component<ReDNATCOMsp.Props, State> {
enabled={hasNucleic}
switchedOn={this.state.display.showNucleic}
onClick={() => {
const display = {
const display: Display = {
...this.state.display,
showNucleic: !this.state.display.showNucleic,
};
......@@ -1040,7 +1093,7 @@ class ReDNATCOMsp extends React.Component<ReDNATCOMsp.Props, State> {
enabled={hasProtein}
switchedOn={this.state.display.showProtein}
onClick={() => {
const display = {
const display: Display = {
...this.state.display,
showProtein: !this.state.display.showProtein,
};
......@@ -1055,7 +1108,7 @@ class ReDNATCOMsp extends React.Component<ReDNATCOMsp.Props, State> {
enabled={hasWater}
switchedOn={this.state.display.showWater}
onClick={() => {
const display = {
const display: Display = {
...this.state.display,
showWater: !this.state.display.showWater,
};
......@@ -1075,7 +1128,7 @@ class ReDNATCOMsp extends React.Component<ReDNATCOMsp.Props, State> {
enabled={ready}
switchedOn={this.state.display.showPyramids}
onClick={() => {
const display = {
const display: Display = {
...this.state.display,
showPyramids: !this.state.display.showPyramids,
};
......@@ -1089,7 +1142,7 @@ class ReDNATCOMsp extends React.Component<ReDNATCOMsp.Props, State> {
text={this.state.display.pyramidsTransparent ? 'Transparent' : 'Solid'}
enabled={this.state.display.showPyramids}
onClick={() => {
const display = {
const display: Display = {
...this.state.display,
pyramidsTransparent: !this.state.display.pyramidsTransparent,
};
......@@ -1113,7 +1166,7 @@ class ReDNATCOMsp extends React.Component<ReDNATCOMsp.Props, State> {
text={this.state.display.ballsTransparent ? 'Transparent' : 'Solid'}
enabled={this.state.display.showBalls}
onClick={() => {
const display = {
const display: Display = {
...this.state.display,
ballsTransparent: !this.state.display.ballsTransparent,
};
......@@ -1226,12 +1279,18 @@ class _ReDNATCOMspApi {
throw new Error('ReDNATCOMsp object not bound');
}
bind(target: ReDNATCOMsp) {
_bind(target: ReDNATCOMsp) {
this.target = target;
}
command(cmd: Commands.Cmd) {
this.check();
this.target!.command(cmd);
}
init(elemId: string, onInited?: () => void) {
ReDNATCOMsp.init(elemId, onInited);
return this;
}
loadStructure(data: string) {
......
......@@ -21,6 +21,25 @@ export namespace Step {
return `${compId}${altId ? `.${altId}` : ''}_${seqId}${insCode ? `.${insCode}` : '' }`;
}
function residueDescription(a: string, b: string): { comp: string, altId?: string, resNo: number, insCode?: string }|undefined {
const toksA = a.split('.');
const toksB = b.split('.');
if (toksA.length > 2 || toksB.length > 2)
return void 0;
const resNo = parseInt(toksB[0]);
if (isNaN(resNo))
return void 0;
return {
comp: toksA[0],
altId: toksA.length === 2 ? toksA[1] : void 0,
resNo,
insCode: toksB.length === 2 ? toksB[1] : void 0,
};
}
export function describe(loci: StructureElement.Loci) {
const es = loci.elements[0]; // Ignore multiple selections
......@@ -62,6 +81,96 @@ export namespace Step {
return description;
}
export function fromName(name: string) {
const description: Description = {
model: -1,
entryId: '',
chain: '',
resNo1: -1,
comp1: '',
altId1: void 0,
insCode1: void 0,
resNo2: -1,
comp2: '',
altId2: void 0,
insCode2: void 0,
};
const toks = name.split('_');
if (toks.length !== 6) {
console.error(`String ${name} is not valid step name`);
return void 0;
}
const entryTok = toks[0];
const chain = toks[1];
const res1TokA = toks[2];
const res1TokB = toks[3];
const res2TokA = toks[4];
const res2TokB = toks[5];
const ets = entryTok.split('-');
if (ets.length === 1) {
description.entryId = ets[0];
description.model = 1;
} else if (ets.length === 2) {
description.entryId = ets[0];
const m = parseInt(ets[1].slice(1));
if (isNaN(m)) {
console.error(`String ${name} is not valid step name`);
return void 0;
}
description.model = m;
} else {
console.error(`String ${name} is not valid step name`);
return void 0;
}
if (chain.length !== 1) {
console.error(`String ${name} is not valid step name`);
return void 0;
} else
description.chain = chain;
const res1 = residueDescription(res1TokA, res1TokB);
const res2 = residueDescription(res2TokA, res2TokB);
if (!res1 || !res2) {
console.error(`String ${name} is not valid step name`);
return void 0;
}
description.resNo1 = res1.resNo;
description.comp1 = res1.comp;
description.altId1 = res1.altId;
description.insCode1 = res1.insCode;
description.resNo2 = res2.resNo;
description.comp2 = res2.comp;
description.altId2 = res2.altId;
description.insCode2 = res2.insCode;
return description;
}
export function is(loci: StructureElement.Loci) {
const e = loci.elements[0];
const loc = Location.create(loci.structure, e.unit, e.unit.elements[OrderedSet.getAt(e.indices, 0)]);
const resNo1 = StructureProperties.residue.label_seq_id(loc);
const asymId = StructureProperties.chain.label_asym_id(loc);
for (let idx = 1; idx < OrderedSet.size(e.indices); idx++) {
loc.element = e.unit.elements[OrderedSet.getAt(e.indices, idx)];
const resNo = StructureProperties.residue.label_seq_id(loc);
if (resNo !== resNo1 + 1)
continue;
const _asymId = StructureProperties.chain.label_asym_id(loc);
if (_asymId === asymId)
return true;
}
return false;
}
export function name(description: Description, multipleModels: boolean) {
const res1 = nameResidue(description.resNo1, description.comp1, description.altId1, description.insCode1);
const res2 = nameResidue(description.resNo2, description.comp2, description.altId2, description.insCode2);
......
import { Segmentation } from '../../mol-data/int';
import { OrderedSet } from '../../mol-data/int/ordered-set';
import { EmptyLoci, Loci } from '../../mol-model/loci';
import { ResidueIndex, Structure, StructureElement, StructureProperties, Unit } from '../../mol-model/structure';
import { Location } from '../../mol-model/structure/structure/element/location';
export namespace Traverse {
type Residue = Segmentation.Segment<ResidueIndex>;
export function residueAltId(structure: Structure, unit: Unit, residue: Residue) {
const loc = Location.create(structure, unit);
for (let rI = residue.start; rI < residue.end; rI++) {
loc.element = OrderedSet.getAt(unit.elements, rI);
const altId = StructureProperties.atom.label_alt_id(loc);
if (altId !== '')
return altId;
}
return void 0;
}
export function findResidue(asymId: string, seqId: number, altId: string|undefined, loci: StructureElement.Loci, source: 'label'|'auth') {
for (const e of loci.elements) {
const loc = Location.create(loci.structure, e.unit);
const getAsymId = source === 'label' ? StructureProperties.chain.label_asym_id : StructureProperties.chain.auth_asym_id;
const getSeqId = source === 'label' ? StructureProperties.residue.label_seq_id : StructureProperties.residue.auth_seq_id;
// Walk the entire unit and look for the requested residue
const chainIt = Segmentation.transientSegments(e.unit.model.atomicHierarchy.chainAtomSegments, e.unit.elements);
const residueIt = Segmentation.transientSegments(e.unit.model.atomicHierarchy.residueAtomSegments, e.unit.elements);
const elemIndex = (idx: number) => OrderedSet.getAt(e.unit.elements, idx);
while (chainIt.hasNext) {
const chain = chainIt.move();
loc.element = elemIndex(chain.start);
const _asymId = getAsymId(loc);
if (_asymId !== asymId)
continue; // Wrong chain, skip it
residueIt.setSegment(chain);
while (residueIt.hasNext) {
const residue = residueIt.move();
loc.element = elemIndex(residue.start);
const _seqId = getSeqId(loc);
if (_seqId === seqId) {
if (altId) {
const _altId = residueAltId(loci.structure, e.unit, residue);
if (_altId && _altId !== altId)
continue;
}
const start = residue.start as StructureElement.UnitIndex;
const end = residue.end as StructureElement.UnitIndex;
return StructureElement.Loci(
loci.structure,
[{ unit: e.unit, indices: OrderedSet.ofBounds(start, end) }]
);
}
}
}
}
return EmptyLoci;
}
export function residue(shift: number, altId: string|undefined, cursor: StructureElement.Loci) {
for (const e of cursor.elements) {
const entireUnit = cursor.structure.units[e.unit.id];
const loc = Location.create(cursor.structure, e.unit);
loc.element = e.unit.elements[OrderedSet.getAt(e.indices, 0)];
const asymId = StructureProperties.chain.label_asym_id(loc);
const seqId = StructureProperties.residue.label_seq_id(loc);
const from = 0 as StructureElement.UnitIndex;
const to = entireUnit.elements.length as StructureElement.UnitIndex;
const loci = findResidue(
asymId,
seqId + shift,
altId,
StructureElement.Loci(cursor.structure, [{ unit: entireUnit, indices: OrderedSet.ofBounds(from, to) }]),
'label'
);
if (!Loci.isEmpty(loci))
return loci;
}
return EmptyLoci;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment