UNPKG

molstar

Version:

A comprehensive macromolecular library.

265 lines (264 loc) 11.5 kB
/** * Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> * @author David Sehnal <david.sehnal@gmail.com> */ import { Unit, StructureElement, Bond, Structure } from '../../../../mol-model/structure'; import { OrderedSet, Interval } from '../../../../mol-data/int'; import { EmptyLoci } from '../../../../mol-model/loci'; import { LocationIterator } from '../../../../mol-geo/util/location-iterator'; import { getResidueLoci } from './common'; export * from './polymer/backbone'; export * from './polymer/gap-iterator'; export * from './polymer/trace-iterator'; export * from './polymer/curve-segment'; export const StandardTension = 0.5; export const HelixTension = 0.9; export const StandardShift = 0.5; export const NucleicShift = 0.3; export const OverhangFactor = 2; export function getPolymerRanges(unit) { switch (unit.kind) { case Unit.Kind.Atomic: return unit.model.atomicRanges.polymerRanges; case Unit.Kind.Spheres: return unit.model.coarseHierarchy.spheres.polymerRanges; case Unit.Kind.Gaussians: return unit.model.coarseHierarchy.gaussians.polymerRanges; } } export function getGapRanges(unit) { switch (unit.kind) { case Unit.Kind.Atomic: return unit.model.atomicRanges.gapRanges; case Unit.Kind.Spheres: return unit.model.coarseHierarchy.spheres.gapRanges; case Unit.Kind.Gaussians: return unit.model.coarseHierarchy.gaussians.gapRanges; } } export var PolymerLocationIterator; (function (PolymerLocationIterator) { function fromGroup(structureGroup, options) { const { group, structure } = structureGroup; const polymerElements = group.units[0].polymerElements; const groupCount = polymerElements.length; const instanceCount = group.units.length; const location = StructureElement.Location.create(structure); const getLocation = (groupIndex, instanceIndex) => { const unit = group.units[instanceIndex]; location.unit = unit; location.element = polymerElements[groupIndex]; return location; }; const asSecondary = !!(options === null || options === void 0 ? void 0 : options.asSecondary); function isSecondary(elementIndex, instanceIndex) { return asSecondary; } return LocationIterator(groupCount, instanceCount, 1, getLocation, false, isSecondary); } PolymerLocationIterator.fromGroup = fromGroup; })(PolymerLocationIterator || (PolymerLocationIterator = {})); export var PolymerGapLocationIterator; (function (PolymerGapLocationIterator) { function fromGroup(structureGroup, options) { const { group, structure } = structureGroup; const gapElements = group.units[0].gapElements; const groupCount = gapElements.length; const instanceCount = group.units.length; const location = StructureElement.Location.create(structure); const getLocation = (groupIndex, instanceIndex) => { const unit = group.units[instanceIndex]; location.unit = unit; location.element = gapElements[groupIndex]; return location; }; const asSecondary = !!(options === null || options === void 0 ? void 0 : options.asSecondary); function isSecondary(elementIndex, instanceIndex) { return asSecondary; } return LocationIterator(groupCount, instanceCount, 1, getLocation, false, isSecondary); } PolymerGapLocationIterator.fromGroup = fromGroup; })(PolymerGapLocationIterator || (PolymerGapLocationIterator = {})); /** Return a Loci for the elements of the whole residue of a polymer element. */ export function getPolymerElementLoci(pickingId, structureGroup, id) { const { objectId, instanceId, groupId } = pickingId; if (id === objectId) { const { structure, group } = structureGroup; const unit = group.units[instanceId]; if (Unit.isAtomic(unit)) { return getResidueLoci(structure, unit, unit.polymerElements[groupId]); } else { const { elements } = unit; const elementIndex = unit.polymerElements[groupId]; const unitIndex = OrderedSet.indexOf(elements, elementIndex); if (unitIndex !== -1) { const indices = OrderedSet.ofSingleton(unitIndex); return StructureElement.Loci(structure, [{ unit, indices }]); } } } return EmptyLoci; } function tryApplyResidueInterval(offset, elements, traceElementIndex, apply, r1, r2) { let start = -1, startIdx = -1; for (let rI = r1; rI <= r2; rI++) { const eI = traceElementIndex[rI]; if (eI < 0) continue; start = OrderedSet.indexOf(elements, eI); if (start >= 0) { startIdx = rI; break; } } if (start < 0) { return false; } let end = start; for (let rI = r2; rI > startIdx; rI--) { const eI = traceElementIndex[rI]; if (eI < 0) continue; const e = OrderedSet.indexOf(elements, eI); if (e >= 0) { end = e; break; } } return apply(Interval.ofRange(offset + start, offset + end)); } export function eachAtomicUnitTracedElement(offset, groupSize, elementsSelector, apply, e) { let changed = false; const { elements } = e.unit; const { traceElementIndex } = e.unit.model.atomicHierarchy.derived.residue; const { index: resIndex } = e.unit.model.atomicHierarchy.residueAtomSegments; const tracedElements = elementsSelector(e.unit); if (Interval.is(e.indices)) { if (Interval.start(e.indices) === 0 && Interval.end(e.indices) === e.unit.elements.length) { // full unit here changed = apply(Interval.ofBounds(offset, offset + groupSize)) || changed; } else { const r1 = resIndex[elements[Interval.min(e.indices)]]; const r2 = resIndex[elements[Interval.max(e.indices)]]; changed = tryApplyResidueInterval(offset, tracedElements, traceElementIndex, apply, r1, r2) || changed; } } else { const { indices } = e; for (let i = 0, _i = indices.length; i < _i; i++) { const r1 = resIndex[elements[indices[i]]]; let r2 = r1; let endI = i + 1; while (endI < _i) { const _r = resIndex[elements[indices[endI]]]; if (_r - r2 > 1) break; r2 = _r; endI++; } i = endI - 1; changed = tryApplyResidueInterval(offset, tracedElements, traceElementIndex, apply, r1, r2) || changed; } } return changed; } function selectPolymerElements(u) { return u.polymerElements; } /** Mark a polymer element (e.g. part of a cartoon trace) */ export function eachPolymerElement(loci, structureGroup, apply) { let changed = false; if (!StructureElement.Loci.is(loci)) return false; const { structure, group } = structureGroup; if (!Structure.areEquivalent(loci.structure, structure)) return false; const groupCount = group.units[0].polymerElements.length; for (const e of loci.elements) { if (!group.unitIndexMap.has(e.unit.id)) continue; const offset = group.unitIndexMap.get(e.unit.id) * groupCount; // to target unit instance if (Unit.isAtomic(e.unit)) { changed = eachAtomicUnitTracedElement(offset, groupCount, selectPolymerElements, apply, e) || changed; } else { if (Interval.is(e.indices)) { const start = offset + Interval.start(e.indices); const end = offset + Interval.end(e.indices); changed = apply(Interval.ofBounds(start, end)) || changed; } else { for (let i = 0, _i = e.indices.length; i < _i; i++) { const start = e.indices[i]; let endI = i + 1; while (endI < _i && e.indices[endI] === start) endI++; i = endI - 1; const end = e.indices[i]; changed = apply(Interval.ofRange(offset + start, offset + end)) || changed; } } } } return changed; } /** Return a Loci for both directions of the polymer gap element. */ export function getPolymerGapElementLoci(pickingId, structureGroup, id) { const { objectId, instanceId, groupId } = pickingId; if (id === objectId) { const { structure, group } = structureGroup; const unit = group.units[instanceId]; const unitIndexA = OrderedSet.indexOf(unit.elements, unit.gapElements[groupId]); const unitIndexB = OrderedSet.indexOf(unit.elements, unit.gapElements[groupId % 2 ? groupId - 1 : groupId + 1]); if (unitIndexA !== -1 && unitIndexB !== -1) { return Bond.Loci(structure, [ Bond.Location(structure, unit, unitIndexA, structure, unit, unitIndexB), Bond.Location(structure, unit, unitIndexB, structure, unit, unitIndexA) ]); } } return EmptyLoci; } export function eachPolymerGapElement(loci, structureGroup, apply) { let changed = false; if (Bond.isLoci(loci)) { const { structure, group } = structureGroup; if (!Structure.areRootsEquivalent(loci.structure, structure)) return false; loci = Bond.remapLoci(loci, structure); const groupCount = group.units[0].gapElements.length; for (const b of loci.bonds) { const unitIdx = group.unitIndexMap.get(b.aUnit.id); if (unitIdx !== undefined) { const idxA = OrderedSet.indexOf(b.aUnit.gapElements, b.aUnit.elements[b.aIndex]); const idxB = OrderedSet.indexOf(b.bUnit.gapElements, b.bUnit.elements[b.bIndex]); if (idxA !== -1 && idxB !== -1) { if (apply(Interval.ofSingleton(unitIdx * groupCount + idxA))) changed = true; } } } } else if (StructureElement.Loci.is(loci)) { const { structure, group } = structureGroup; if (!Structure.areRootsEquivalent(loci.structure, structure)) return false; loci = StructureElement.Loci.remap(loci, structure); const groupCount = group.units[0].gapElements.length; for (const e of loci.elements) { const unitIdx = group.unitIndexMap.get(e.unit.id); if (unitIdx !== undefined) { OrderedSet.forEach(e.indices, v => { const idx = OrderedSet.indexOf(e.unit.gapElements, e.unit.elements[v]); if (idx !== -1) { if (apply(Interval.ofSingleton(unitIdx * groupCount + idx))) changed = true; // need to check the next element in case of consecutive gaps if (OrderedSet.getAt(e.unit.gapElements, idx + 1) === e.unit.elements[v]) { if (apply(Interval.ofSingleton(unitIdx * groupCount + idx + 1))) changed = true; } } }); } } } return changed; }