diff --git a/src/mol-data/int/impl/interval.ts b/src/mol-data/int/impl/interval.ts index f7568480900db112b77c934e7e4d856a923062b8..fe293e0a0c013897a68b07321349a5e8277ee6e9 100644 --- a/src/mol-data/int/impl/interval.ts +++ b/src/mol-data/int/impl/interval.ts @@ -19,6 +19,7 @@ export function size(i: Tuple) { return Tuple.snd(i) - Tuple.fst(i); } export const hashCode = Tuple.hashCode; export function has(int: Tuple, v: number) { return Tuple.fst(int) <= v && v < Tuple.snd(int); } +/** Returns the index of `x` in `set` or -1 if not found. */ export function indexOf(int: Tuple, x: number) { const m = start(int); return x >= m && x < end(int) ? x - m : -1; } export function getAt(int: Tuple, i: number) { return Tuple.fst(int) + i; } diff --git a/src/mol-data/int/impl/ordered-set.ts b/src/mol-data/int/impl/ordered-set.ts index 27ee1f1db798f09ebc789a2043520656c63ebec3..33794046c652e26bba6469695c510d928ad46977 100644 --- a/src/mol-data/int/impl/ordered-set.ts +++ b/src/mol-data/int/impl/ordered-set.ts @@ -25,6 +25,7 @@ export function ofSortedArray(xs: Nums): OrderedSetImpl { export function size(set: OrderedSetImpl) { return I.is(set) ? I.size(set) : S.size(set); } export function has(set: OrderedSetImpl, x: number) { return I.is(set) ? I.has(set, x) : S.has(set, x); } +/** Returns the index of `x` in `set` or -1 if not found. */ export function indexOf(set: OrderedSetImpl, x: number) { return I.is(set) ? I.indexOf(set, x) : S.indexOf(set, x); } export function getAt(set: OrderedSetImpl, i: number) { return I.is(set) ? I.getAt(set, i) : set[i]; } export function min(set: OrderedSetImpl) { return I.is(set) ? I.min(set) : S.min(set); } diff --git a/src/mol-data/int/impl/sorted-array.ts b/src/mol-data/int/impl/sorted-array.ts index 2d441bc3fdf4304e7808e9a417d64a98060441a6..6fff1aa007c47f26cc99d19e8ed790e0e57c4398 100644 --- a/src/mol-data/int/impl/sorted-array.ts +++ b/src/mol-data/int/impl/sorted-array.ts @@ -36,6 +36,7 @@ export function hashCode(xs: Nums) { return hash3(s, xs[0], xs[s - 1]); } +/** Returns the index of `x` in `set` or -1 if not found. */ export function indexOf(xs: Nums, v: number) { const l = xs.length; return l === 0 ? -1 : xs[0] <= v && v <= xs[l - 1] ? binarySearchRange(xs, v, 0, l) : -1; diff --git a/src/mol-data/int/interval.ts b/src/mol-data/int/interval.ts index b2ab7fb8350624c57d88f4d39960e6f41465abe7..ef72450b05865a03ecbffc4562e06c81ada34f49 100644 --- a/src/mol-data/int/interval.ts +++ b/src/mol-data/int/interval.ts @@ -18,6 +18,7 @@ namespace Interval { /** Test if a value is within the bounds of the interval */ export const has: <T extends number = number>(interval: Interval<T>, x: T) => boolean = Impl.has as any; + /** Returns the index of `x` in `set` or -1 if not found. */ export const indexOf: <T extends number = number>(interval: Interval<T>, x: T) => number = Impl.indexOf as any; export const getAt: <T extends number = number>(interval: Interval<T>, i: number) => T = Impl.getAt as any; diff --git a/src/mol-data/int/ordered-set.ts b/src/mol-data/int/ordered-set.ts index d18e61446d77582a7c5e8d6af78c586db838f875..046c1b6ab94b3acf93b5b5d232eb711c8b054402 100644 --- a/src/mol-data/int/ordered-set.ts +++ b/src/mol-data/int/ordered-set.ts @@ -18,6 +18,7 @@ namespace OrderedSet { export const ofSortedArray: <T extends number = number>(xs: ArrayLike<T>) => OrderedSet<T> = Base.ofSortedArray as any; export const has: <T extends number = number>(set: OrderedSet<T>, x: T) => boolean = Base.has as any; + /** Returns the index of `x` in `set` or -1 if not found. */ export const indexOf: <T extends number = number>(set: OrderedSet<T>, x: T) => number = Base.indexOf as any; export const getAt: <T extends number = number>(set: OrderedSet<T>, i: number) => T = Base.getAt as any; diff --git a/src/mol-data/int/sorted-array.ts b/src/mol-data/int/sorted-array.ts index c0f1248db3f25e3c7764c67164412d1baafb0c7a..cf0f5d11e09470a4b520cb00707faf4e7f034ea3 100644 --- a/src/mol-data/int/sorted-array.ts +++ b/src/mol-data/int/sorted-array.ts @@ -19,6 +19,7 @@ namespace SortedArray { export const is: <T extends number = number>(v: any) => v is SortedArray<T> = Impl.is as any; export const has: <T extends number = number>(array: SortedArray<T>, x: T) => boolean = Impl.has as any; + /** Returns the index of `x` in `set` or -1 if not found. */ export const indexOf: <T extends number = number>(array: SortedArray<T>, x: T) => number = Impl.indexOf as any; export const indexOfInInterval: <T extends number = number>(array: SortedArray<T>, x: number, bounds: Interval) => number = Impl.indexOfInInterval as any; diff --git a/src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts b/src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts index dca53ec6fe4b9beab83016395e6139a1a2623bc0..185835c629e4bcca92a85f02008992f9466199df 100644 --- a/src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts +++ b/src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts @@ -92,8 +92,8 @@ function CarbohydrateLinkIterator(structure: Structure): LocationIterator { const link = links[groupIndex] const carbA = elements[link.carbohydrateIndexA] const carbB = elements[link.carbohydrateIndexB] - const indexA = OrderedSet.findPredecessorIndex(carbA.unit.elements, carbA.anomericCarbon) - const indexB = OrderedSet.findPredecessorIndex(carbB.unit.elements, carbB.anomericCarbon) + const indexA = OrderedSet.indexOf(carbA.unit.elements, carbA.anomericCarbon) + const indexB = OrderedSet.indexOf(carbB.unit.elements, carbB.anomericCarbon) location.aUnit = carbA.unit location.aIndex = indexA as StructureElement.UnitIndex location.bUnit = carbB.unit @@ -110,14 +110,16 @@ function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) { const l = links[groupId] const carbA = elements[l.carbohydrateIndexA] const carbB = elements[l.carbohydrateIndexB] - const indexA = OrderedSet.findPredecessorIndex(carbA.unit.elements, carbA.anomericCarbon) - const indexB = OrderedSet.findPredecessorIndex(carbB.unit.elements, carbB.anomericCarbon) - return Link.Loci([ - Link.Location( - carbA.unit, indexA as StructureElement.UnitIndex, - carbB.unit, indexB as StructureElement.UnitIndex - ) - ]) + const indexA = OrderedSet.indexOf(carbA.unit.elements, carbA.anomericCarbon) + const indexB = OrderedSet.indexOf(carbB.unit.elements, carbB.anomericCarbon) + if (indexA !== -1 && indexB !== -1) { + return Link.Loci([ + Link.Location( + carbA.unit, indexA as StructureElement.UnitIndex, + carbB.unit, indexB as StructureElement.UnitIndex + ) + ]) + } } return EmptyLoci } diff --git a/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts b/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts index 309319c00520fff4fbe60e499c787db5a19e717c..13d568ccb73cd097c659a36a8281c9e600b4aa85 100644 --- a/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts +++ b/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts @@ -182,9 +182,11 @@ function getCarbohydrateLoci(pickingId: PickingId, structure: Structure, id: num if (id === objectId) { const carb = structure.carbohydrates.elements[Math.floor(groupId / 2)] const { unit } = carb - const index = OrderedSet.findPredecessorIndex(unit.elements, carb.anomericCarbon) - const indices = OrderedSet.ofSingleton(index as StructureElement.UnitIndex) - return StructureElement.Loci([{ unit, indices }]) + const index = OrderedSet.indexOf(unit.elements, carb.anomericCarbon) + if (index !== -1) { + const indices = OrderedSet.ofSingleton(index as StructureElement.UnitIndex) + return StructureElement.Loci([{ unit, indices }]) + } } return EmptyLoci } diff --git a/src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts b/src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts index f7d82a34a3de4967a425a480c6355dea118ee598..9b9452a4191d579a07837137b34ccb22082261ae 100644 --- a/src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts +++ b/src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts @@ -102,12 +102,13 @@ function markLink(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Inter let changed = false if (Unit.isAtomic(unit) && Link.isLoci(loci)) { + const groupCount = unit.links.edgeCount * 2 for (const b of loci.links) { const unitIdx = group.unitIndexMap.get(b.aUnit.id) if (unitIdx !== undefined) { const idx = unit.links.getDirectedEdgeIndex(b.aIndex, b.bIndex) if (idx !== -1) { - if (apply(Interval.ofSingleton(idx))) changed = true + if (apply(Interval.ofSingleton(unitIdx * groupCount + idx))) changed = true } } } diff --git a/src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts b/src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts index 8591ca379aca4372f51ec30ea854281c09c2a039..8b5d893ef85dc1b6ae8b18ea8a651c3427e8872d 100644 --- a/src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts +++ b/src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts @@ -9,7 +9,6 @@ import { UnitsVisual } from '..'; import { RuntimeContext } from 'mol-task' import { Mesh } from '../../../mesh/mesh'; import { MeshBuilder } from '../../../mesh/mesh-builder'; -import { getElementLoci, markElement } from './util/element'; import { Vec3, Mat4 } from 'mol-math/linear-algebra'; import { Segmentation } from 'mol-data/int'; import { MoleculeType, isNucleic, isPurinBase, isPyrimidineBase } from 'mol-model/structure/model/types'; @@ -17,7 +16,7 @@ import { getElementIndexForAtomId, getElementIndexForAtomRole } from 'mol-model/ import { DefaultUnitsMeshProps, UnitsMeshVisual } from '../units-visual'; import { addCylinder } from '../../../mesh/builder/cylinder'; import { Box } from '../../../primitive/box'; -import { NucleotideLocationIterator } from './util/nucleotide'; +import { NucleotideLocationIterator, markNucleotideElement, getNucleotideElementLoci } from './util/nucleotide'; const p1 = Vec3.zero() const p2 = Vec3.zero() @@ -121,8 +120,8 @@ export function NucleotideBlockVisual(): UnitsVisual<NucleotideBlockProps> { defaultProps: DefaultNucleotideBlockProps, createMesh: createNucleotideBlockMesh, createLocationIterator: NucleotideLocationIterator.fromGroup, - getLoci: getElementLoci, - mark: markElement, + getLoci: getNucleotideElementLoci, + mark: markNucleotideElement, setUpdateState: () => {} }) } \ No newline at end of file diff --git a/src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts b/src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts index dd277116de925dbc3d0d210626d797167e536e9d..d975ae940228601a3fd1a833979d28dc2c0dc96e 100644 --- a/src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts +++ b/src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts @@ -47,11 +47,11 @@ async function createPolymerBackboneCylinderMesh(ctx: RuntimeContext, unit: Unit pos(centerB.element, pB) cylinderProps.radiusTop = cylinderProps.radiusBottom = sizeTheme.size(centerA) - builder.setGroup(OrderedSet.findPredecessorIndex(elements, centerA.element)) + builder.setGroup(OrderedSet.indexOf(elements, centerA.element)) addCylinder(builder, pA, pB, 0.5, cylinderProps) cylinderProps.radiusTop = cylinderProps.radiusBottom = sizeTheme.size(centerB) - builder.setGroup(OrderedSet.findPredecessorIndex(elements, centerB.element)) + builder.setGroup(OrderedSet.indexOf(elements, centerB.element)) addCylinder(builder, pB, pA, 0.5, cylinderProps) if (i % 10000 === 0 && ctx.shouldUpdate) { diff --git a/src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts b/src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts index e5a319c08e9e83a8c760145664228697696cc6bf..24038dfc3182746c74d2ae0336a5556ff00cfcde 100644 --- a/src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts +++ b/src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts @@ -9,8 +9,7 @@ import { UnitsVisual, MeshUpdateState } from '..'; import { RuntimeContext } from 'mol-task' import { Mesh } from '../../../mesh/mesh'; import { MeshBuilder } from '../../../mesh/mesh-builder'; -import { PolymerGapIterator, PolymerGapLocationIterator } from './util/polymer'; -import { getElementLoci, markElement } from './util/element'; +import { PolymerGapIterator, PolymerGapLocationIterator, markPolymerGapElement, getPolymerGapElementLoci } from './util/polymer'; import { Vec3 } from 'mol-math/linear-algebra'; import { UnitsMeshVisual, DefaultUnitsMeshProps } from '../units-visual'; import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size'; @@ -83,8 +82,8 @@ export function PolymerGapVisual(): UnitsVisual<PolymerGapProps> { defaultProps: DefaultPolymerGapProps, createMesh: createPolymerGapCylinderMesh, createLocationIterator: PolymerGapLocationIterator.fromGroup, - getLoci: getElementLoci, - mark: markElement, + getLoci: getPolymerGapElementLoci, + mark: markPolymerGapElement, setUpdateState: (state: MeshUpdateState, newProps: PolymerGapProps, currentProps: PolymerGapProps) => { state.createMesh = newProps.radialSegments !== currentProps.radialSegments } diff --git a/src/mol-geo/representation/structure/visual/util/nucleotide.ts b/src/mol-geo/representation/structure/visual/util/nucleotide.ts index 2243e8a6bb32f1719b5f68f16b268c18bae6d49b..ab334d1d0d156d8043f129a9a20999fd14349192 100644 --- a/src/mol-geo/representation/structure/visual/util/nucleotide.ts +++ b/src/mol-geo/representation/structure/visual/util/nucleotide.ts @@ -3,40 +3,18 @@ * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Unit, ElementIndex, StructureElement } from 'mol-model/structure'; -import { LocationIterator } from '../../../../util/location-iterator'; -import { Segmentation } from 'mol-data/int'; -import { isNucleic, MoleculeType } from 'mol-model/structure/model/types'; -import { getElementIndexForAtomRole } from 'mol-model/structure/util'; - -export function getNucleotideElementIndices(unit: Unit) { - const indices: ElementIndex[] = [] - const { elements, model } = unit - const { chemicalComponentMap } = model.properties - const { chainAtomSegments, residueAtomSegments, residues } = model.atomicHierarchy - const { label_comp_id } = residues - const chainIt = Segmentation.transientSegments(chainAtomSegments, elements) - const residueIt = Segmentation.transientSegments(residueAtomSegments, elements) - while (chainIt.hasNext) { - residueIt.setSegment(chainIt.move()); - - while (residueIt.hasNext) { - const { index } = residueIt.move(); - const cc = chemicalComponentMap.get(label_comp_id.value(index)) - const moleculeType = cc ? cc.moleculeType : MoleculeType.unknown - if (isNucleic(moleculeType)) { - const elementIndex = getElementIndexForAtomRole(model, index, 'trace') - indices.push(elementIndex === -1 ? residueAtomSegments.offsets[index] : elementIndex) - } - } - } - return indices -} +import { Unit, StructureElement } from 'mol-model/structure'; +import { LocationIterator } from '../../../../util/location-iterator'; +import { getNucleotideElements } from 'mol-model/structure/structure/util/nucleotide'; +import { PickingId } from '../../../../util/picking'; +import { Loci, EmptyLoci } from 'mol-model/loci'; +import { OrderedSet, Interval } from 'mol-data/int'; export namespace NucleotideLocationIterator { export function fromGroup(group: Unit.SymmetryGroup): LocationIterator { - const nucleotideElementIndices = getNucleotideElementIndices(group.units[0]) + const u = group.units[0] + const nucleotideElementIndices = Unit.isAtomic(u) ? getNucleotideElements(u) : [] const groupCount = nucleotideElementIndices.length const instanceCount = group.units.length const location = StructureElement.create() @@ -48,4 +26,47 @@ export namespace NucleotideLocationIterator { } return LocationIterator(groupCount, instanceCount, getLocation) } +} + +export function getNucleotideElementLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number) { + const { objectId, instanceId, groupId } = pickingId + if (id === objectId) { + const unit = group.units[instanceId] + if (Unit.isAtomic(unit)) { + const unitIndex = OrderedSet.indexOf(unit.elements, unit.nucleotideElements[groupId]) as StructureElement.UnitIndex + if (unitIndex !== -1) { + const indices = OrderedSet.ofSingleton(unitIndex) + return StructureElement.Loci([{ unit, indices }]) + } + } + } + return EmptyLoci +} + +export function markNucleotideElement(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Interval) => boolean) { + let changed = false + const u = group.units[0] + if (StructureElement.isLoci(loci) && Unit.isAtomic(u)) { + const groupCount = u.nucleotideElements.length + for (const e of loci.elements) { + const unitIdx = group.unitIndexMap.get(e.unit.id) + if (unitIdx !== undefined && Unit.isAtomic(e.unit)) { + if (Interval.is(e.indices)) { + const min = unitIdx * groupCount + OrderedSet.indexOf(e.unit.nucleotideElements, e.unit.elements[Interval.min(e.indices)]) + const max = unitIdx * groupCount + OrderedSet.indexOf(e.unit.nucleotideElements, e.unit.elements[Interval.max(e.indices)]) + if (min !== -1 && max !== -1) { + if (apply(Interval.ofRange(unitIdx * groupCount + min, unitIdx * groupCount + max))) changed = true + } + } else { + for (let i = 0, _i = e.indices.length; i < _i; i++) { + const idx = OrderedSet.indexOf(e.unit.nucleotideElements, e.unit.elements[e.indices[i]]) + if (idx !== -1) { + if (apply(Interval.ofSingleton(unitIdx * groupCount + idx))) changed = true + } + } + } + } + } + } + return changed } \ No newline at end of file diff --git a/src/mol-geo/representation/structure/visual/util/polymer.ts b/src/mol-geo/representation/structure/visual/util/polymer.ts index 41458be2e366e10e957d6bc7af871694b9806f09..8586b9e50ad5188b6ab1d7a0b37e3d70944a112d 100644 --- a/src/mol-geo/representation/structure/visual/util/polymer.ts +++ b/src/mol-geo/representation/structure/visual/util/polymer.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Unit, ElementIndex, StructureElement } from 'mol-model/structure'; +import { Unit, ElementIndex, StructureElement, Link } from 'mol-model/structure'; import SortedRanges from 'mol-data/int/sorted-ranges'; import { LocationIterator } from '../../../../util/location-iterator'; import { PickingId } from '../../../../util/picking'; @@ -68,9 +68,11 @@ export function getPolymerElementLoci(pickingId: PickingId, group: Unit.Symmetry const { objectId, instanceId, groupId } = pickingId if (id === objectId) { const unit = group.units[instanceId] - const unitIndex = OrderedSet.findPredecessorIndex(unit.elements, unit.polymerElements[groupId]) as StructureElement.UnitIndex - const indices = OrderedSet.ofSingleton(unitIndex); - return StructureElement.Loci([{ unit, indices }]) + const unitIndex = OrderedSet.indexOf(unit.elements, unit.polymerElements[groupId]) as StructureElement.UnitIndex + if (unitIndex !== -1) { + const indices = OrderedSet.ofSingleton(unitIndex) + return StructureElement.Loci([{ unit, indices }]) + } } return EmptyLoci } @@ -84,17 +86,52 @@ export function markPolymerElement(loci: Loci, group: Unit.SymmetryGroup, apply: const unitIdx = group.unitIndexMap.get(e.unit.id) if (unitIdx !== undefined) { if (Interval.is(e.indices)) { - const start = unitIdx * groupCount + OrderedSet.findPredecessorIndex(e.unit.polymerElements, e.unit.elements[Interval.start(e.indices)]) - const end = unitIdx * groupCount + OrderedSet.findPredecessorIndex(e.unit.polymerElements, e.unit.elements[Interval.end(e.indices)]) - if (apply(Interval.ofBounds(start, end))) changed = true + const min = + OrderedSet.indexOf(e.unit.polymerElements, e.unit.elements[Interval.min(e.indices)]) + const max = OrderedSet.indexOf(e.unit.polymerElements, e.unit.elements[Interval.max(e.indices)]) + if (min !== -1 && max !== -1) { + if (apply(Interval.ofRange(unitIdx * groupCount + min, unitIdx * groupCount + max))) changed = true + } } else { for (let i = 0, _i = e.indices.length; i < _i; i++) { - const idx = unitIdx * groupCount + OrderedSet.findPredecessorIndex(e.unit.polymerElements, e.unit.elements[e.indices[i]]) - if (apply(Interval.ofSingleton(idx))) changed = true + const idx = OrderedSet.indexOf(e.unit.polymerElements, e.unit.elements[e.indices[i]]) + if (idx !== -1) { + if (apply(Interval.ofSingleton(unitIdx * groupCount + idx))) changed = true + } } } } } } return changed +} + +export function getPolymerGapElementLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number) { + const { objectId, instanceId, groupId } = pickingId + if (id === objectId) { + const unit = group.units[instanceId] + const unitIndexA = OrderedSet.indexOf(unit.elements, unit.gapElements[groupId]) as StructureElement.UnitIndex + const unitIndexB = OrderedSet.indexOf(unit.elements, unit.gapElements[groupId % 2 ? groupId - 1 : groupId + 1]) as StructureElement.UnitIndex + if (unitIndexA !== -1 && unitIndexB !== -1) { + return Link.Loci([ Link.Location(unit, unitIndexA, unit, unitIndexB) ]) + } + } + return EmptyLoci +} + +export function markPolymerGapElement(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Interval) => boolean) { + let changed = false + if (Link.isLoci(loci)) { + const groupCount = group.units[0].gapElements.length + for (const b of loci.links) { + 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 + } + } + } + } + return changed } \ No newline at end of file diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts index 3cd9ed382ce7a881dc88d94ed80fd3678855d5dd..4092d9d3cbb5521fe300c6c8cf45e35e9f845c0b 100644 --- a/src/mol-model/structure/structure/unit.ts +++ b/src/mol-model/structure/structure/unit.ts @@ -17,6 +17,7 @@ import { ChainIndex, ResidueIndex, ElementIndex } from '../model/indexing'; import { IntMap, SortedArray } from 'mol-data/int'; import { hash2 } from 'mol-data/util'; import { getAtomicPolymerElements, getCoarsePolymerElements, getAtomicGapElements, getCoarseGapElements } from './util/polymer'; +import { getNucleotideElements } from './util/nucleotide'; // A building block of a structure that corresponds to an atomic or a coarse grained representation // 'conveniently grouped together'. @@ -169,6 +170,12 @@ namespace Unit { return this.props.gapElements.ref; } + get nucleotideElements() { + if (this.props.nucleotideElements.ref) return this.props.nucleotideElements.ref; + this.props.nucleotideElements.ref = getNucleotideElements(this); + return this.props.nucleotideElements.ref; + } + getResidueIndex(elementIndex: StructureElement.UnitIndex) { return this.model.atomicHierarchy.residueAtomSegments.index[this.elements[elementIndex]]; } @@ -192,6 +199,7 @@ namespace Unit { rings: ValueRef<UnitRings | undefined> polymerElements: ValueRef<SortedArray<ElementIndex> | undefined> gapElements: ValueRef<SortedArray<ElementIndex> | undefined> + nucleotideElements: ValueRef<SortedArray<ElementIndex> | undefined> } function AtomicProperties(): AtomicProperties { @@ -201,6 +209,7 @@ namespace Unit { rings: ValueRef.create(void 0), polymerElements: ValueRef.create(void 0), gapElements: ValueRef.create(void 0), + nucleotideElements: ValueRef.create(void 0), }; } diff --git a/src/mol-model/structure/structure/util/nucleotide.ts b/src/mol-model/structure/structure/util/nucleotide.ts new file mode 100644 index 0000000000000000000000000000000000000000..8dbb668b74a5330781041f33c30c7d3d23713351 --- /dev/null +++ b/src/mol-model/structure/structure/util/nucleotide.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Unit, ElementIndex } from 'mol-model/structure'; +import { Segmentation, SortedArray } from 'mol-data/int'; +import { isNucleic, MoleculeType } from 'mol-model/structure/model/types'; +import { getElementIndexForAtomRole } from 'mol-model/structure/util'; + +export function getNucleotideElements(unit: Unit.Atomic) { + const indices: ElementIndex[] = [] + const { elements, model } = unit + const { chemicalComponentMap } = model.properties + const { chainAtomSegments, residueAtomSegments, residues } = model.atomicHierarchy + const { label_comp_id } = residues + const chainIt = Segmentation.transientSegments(chainAtomSegments, elements) + const residueIt = Segmentation.transientSegments(residueAtomSegments, elements) + while (chainIt.hasNext) { + residueIt.setSegment(chainIt.move()); + + while (residueIt.hasNext) { + const { index } = residueIt.move(); + const cc = chemicalComponentMap.get(label_comp_id.value(index)) + const moleculeType = cc ? cc.moleculeType : MoleculeType.unknown + + if (isNucleic(moleculeType)) { + const elementIndex = getElementIndexForAtomRole(model, index, 'trace') + indices.push(elementIndex === -1 ? residueAtomSegments.offsets[index] : elementIndex) + } + } + } + return SortedArray.ofSortedArray<ElementIndex>(indices) +} \ No newline at end of file