diff --git a/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts b/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts index a85dddd51386a2f05e1554529d4363533f48789d..2ff4a5d237938553d1938be2a2a4da84a9fd6d94 100644 --- a/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts +++ b/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts @@ -105,6 +105,10 @@ function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) { Link.Location( carbA.unit, indexA as StructureElement.UnitIndex, carbB.unit, indexB as StructureElement.UnitIndex + ), + Link.Location( + carbB.unit, indexB as StructureElement.UnitIndex, + carbA.unit, indexA as StructureElement.UnitIndex ) ]) } diff --git a/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts b/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts index 9a42ca5f74712690b84fe5aa77a2ec001c660391..ba07ae3a78f973fe468237d50406c9571ac03208 100644 --- a/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts +++ b/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts @@ -25,6 +25,7 @@ import { OrderedSet, Interval } from 'mol-data/int'; import { EmptyLoci, Loci } from 'mol-model/loci'; import { VisualContext } from 'mol-repr/representation'; import { Theme } from 'mol-theme/theme'; +import { getResidueLoci } from './util/common'; const t = Mat4.identity() const sVec = Vec3.zero() @@ -184,30 +185,36 @@ function CarbohydrateElementIterator(structure: Structure): LocationIterator { return LocationIterator(groupCount, instanceCount, getLocation, true, isSecondary) } +/** Return a Loci for the elements of the whole residue of a carbohydrate. */ function getCarbohydrateLoci(pickingId: PickingId, structure: Structure, id: number) { const { objectId, groupId } = pickingId if (id === objectId) { const carb = structure.carbohydrates.elements[Math.floor(groupId / 2)] - const { unit } = carb - const index = OrderedSet.indexOf(unit.elements, carb.anomericCarbon) - if (index !== -1) { - const indices = OrderedSet.ofSingleton(index as StructureElement.UnitIndex) - return StructureElement.Loci(structure, [{ unit, indices }]) - } + return getResidueLoci(structure, carb.unit, carb.anomericCarbon) } return EmptyLoci } +/** Mark a carbohydrate (usually a monosaccharide) when all its residue's elements are in a loci. */ function markCarbohydrate(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) { - const { getElementIndex } = structure.carbohydrates - + const { getElementIndex, getAnomericCarbon } = structure.carbohydrates let changed = false if (StructureElement.isLoci(loci)) { for (const e of loci.elements) { - OrderedSet.forEach(e.indices, index => { - const idx = getElementIndex(e.unit, e.unit.elements[index]) - if (idx !== undefined) { - if (apply(Interval.ofBounds(idx * 2, idx * 2 + 2))) changed = true + OrderedSet.forEach(e.indices, v => { + const { model, elements } = e.unit + const { index, offsets } = model.atomicHierarchy.residueAtomSegments + const rI = index[elements[v]] + const unitIndexMin = OrderedSet.findPredecessorIndex(elements, offsets[rI]) + const unitIndexMax = OrderedSet.findPredecessorIndex(elements, offsets[rI + 1] - 1) + const unitIndexInterval = Interval.ofRange(unitIndexMin, unitIndexMax) + if(!OrderedSet.isSubset(e.indices, unitIndexInterval)) return + const eI = getAnomericCarbon(e.unit, rI) + if (eI !== undefined) { + const idx = getElementIndex(e.unit, eI) + if (idx !== undefined) { + if (apply(Interval.ofBounds(idx * 2, idx * 2 + 2))) changed = true + } } }) } diff --git a/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts b/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts index 3c4d57853b4140d8fba7d23ae49c39c39d42dd7c..21eb8ad7b87887b2dc36e39901912c853d78a3f6 100644 --- a/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts +++ b/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts @@ -114,25 +114,21 @@ function getTerminalLinkLoci(pickingId: PickingId, structure: Structure, id: num const carb = elements[l.carbohydrateIndex] const carbIndex = OrderedSet.indexOf(carb.unit.elements, carb.anomericCarbon) - if (l.fromCarbohydrate) { - return Link.Loci(structure, [ - Link.Location( - carb.unit, carbIndex as StructureElement.UnitIndex, - l.elementUnit, l.elementIndex - ) - ]) - } else { - return Link.Loci(structure, [ - Link.Location( - l.elementUnit, l.elementIndex, - carb.unit, carbIndex as StructureElement.UnitIndex - ) - ]) - } + return Link.Loci(structure, [ + Link.Location( + carb.unit, carbIndex as StructureElement.UnitIndex, + l.elementUnit, l.elementIndex + ), + Link.Location( + l.elementUnit, l.elementIndex, + carb.unit, carbIndex as StructureElement.UnitIndex + ) + ]) } return EmptyLoci } +// TODO mark link when both (or one) of the link elements are in a StructureElement.Loci function markTerminalLink(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) { const { getTerminalLinkIndex } = structure.carbohydrates diff --git a/src/mol-repr/structure/visual/util/common.ts b/src/mol-repr/structure/visual/util/common.ts index a9b574a2fb38ac4b161f266c668b38c880ee378c..b1997df8ad476845f94a01e0f13b628cc3e370fc 100644 --- a/src/mol-repr/structure/visual/util/common.ts +++ b/src/mol-repr/structure/visual/util/common.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Unit, Structure } from 'mol-model/structure'; +import { Unit, Structure, ElementIndex, StructureElement } from 'mol-model/structure'; import { createMeshRenderObject, createPointsRenderObject, createLinesRenderObject, createDirectVolumeRenderObject } from 'mol-gl/render-object'; import { Mat4 } from 'mol-math/linear-algebra'; import { TransformData, createTransform, createIdentityTransform } from 'mol-geo/geometry/transform-data'; @@ -18,6 +18,27 @@ import { VisualContext } from 'mol-repr/representation'; import { Theme } from 'mol-theme/theme'; import { ParamDefinition as PD } from 'mol-util/param-definition'; import { StructureMeshParams, StructurePointsParams, StructureLinesParams, StructureDirectVolumeParams } from 'mol-repr/structure/representation'; +import { OrderedSet, SortedArray } from 'mol-data/int'; +import { EmptyLoci, Loci } from 'mol-model/loci'; + +/** Return a Loci for the elements of a whole residue the elementIndex belongs to. */ +export function getResidueLoci(structure: Structure, unit: Unit, elementIndex: ElementIndex): Loci { + const { elements, model } = unit + if (OrderedSet.indexOf(elements, elementIndex) !== -1) { + const { index, offsets } = model.atomicHierarchy.residueAtomSegments + const rI = index[elementIndex] + const _indices: number[] = [] + for (let i = offsets[rI], il = offsets[rI + 1]; i < il; ++i) { + const unitIndex = OrderedSet.indexOf(elements, i) + if (unitIndex !== -1) _indices.push(unitIndex) + } + const indices = OrderedSet.ofSortedArray<StructureElement.UnitIndex>(SortedArray.ofSortedArray(_indices)) + return StructureElement.Loci(structure, [{ unit, indices }]) + } + return EmptyLoci +} + +// export function createUnitsTransform({ units }: Unit.SymmetryGroup, transformData?: TransformData) { const unitCount = units.length diff --git a/src/mol-repr/structure/visual/util/polymer.ts b/src/mol-repr/structure/visual/util/polymer.ts index 65b9d7f3c189e3438ac62bbfbb2feacc8b588c33..3e45613bb5cb1b18cef54bdc31f9419dd9052fa4 100644 --- a/src/mol-repr/structure/visual/util/polymer.ts +++ b/src/mol-repr/structure/visual/util/polymer.ts @@ -6,12 +6,13 @@ import { Unit, ElementIndex, StructureElement, Link } from 'mol-model/structure'; import SortedRanges from 'mol-data/int/sorted-ranges'; -import { OrderedSet, Interval, SortedArray } from 'mol-data/int'; +import { OrderedSet, Interval } from 'mol-data/int'; import { EmptyLoci, Loci } from 'mol-model/loci'; import { LocationIterator } from 'mol-geo/util/location-iterator'; import { PickingId } from 'mol-geo/geometry/picking'; import { StructureGroup } from 'mol-repr/structure/units-visual'; import { getElementIndexForAtomRole } from 'mol-model/structure/util'; +import { getResidueLoci } from './common'; export * from './polymer/backbone-iterator' export * from './polymer/gap-iterator' @@ -66,28 +67,18 @@ export namespace PolymerGapLocationIterator { } } -/** Return a Loci for the elements of a whole residue. */ +/** Return a Loci for the elements of the whole residue of a polymer element. */ export function getPolymerElementLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number) { const { objectId, instanceId, groupId } = pickingId if (id === objectId) { const { structure, group } = structureGroup const unit = group.units[instanceId] - const { elements, polymerElements, model } = unit - if (OrderedSet.indexOf(elements, polymerElements[groupId]) !== -1) { - const { index, offsets } = model.atomicHierarchy.residueAtomSegments - const rI = index[polymerElements[groupId]] - const _indices: number[] = [] - for (let i = offsets[rI], il = offsets[rI + 1]; i < il; ++i) { - const unitIndex = OrderedSet.indexOf(elements, i) - if (unitIndex !== -1) _indices.push(unitIndex) - } - const indices = OrderedSet.ofSortedArray<StructureElement.UnitIndex>(SortedArray.ofSortedArray(_indices)) - return StructureElement.Loci(structure, [{ unit, indices }]) - } + return getResidueLoci(structure, unit, unit.polymerElements[groupId]) } return EmptyLoci } +/** Mark a polymer element (e.g. part of a cartoon trace) when all its residue's elements are in a loci. */ export function markPolymerElement(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) { let changed = false if (!StructureElement.isLoci(loci)) return false @@ -102,9 +93,9 @@ export function markPolymerElement(loci: Loci, structureGroup: StructureGroup, a // TODO optimized implementation for intervals OrderedSet.forEach(e.indices, v => { const rI = index[elements[v]] - const unitIndexBeg = OrderedSet.indexOf(elements, offsets[rI]) - const unitIndexEnd = OrderedSet.indexOf(elements, offsets[rI + 1]) - const unitIndexInterval = Interval.ofBounds(unitIndexBeg, unitIndexEnd) + const unitIndexMin = OrderedSet.findPredecessorIndex(elements, offsets[rI]) + const unitIndexMax = OrderedSet.findPredecessorIndex(elements, offsets[rI + 1] - 1) + const unitIndexInterval = Interval.ofRange(unitIndexMin, unitIndexMax) if(!OrderedSet.isSubset(e.indices, unitIndexInterval)) return const eI = getElementIndexForAtomRole(model, rI, 'trace') const idx = OrderedSet.indexOf(e.unit.polymerElements, eI) @@ -117,6 +108,7 @@ export function markPolymerElement(loci: Loci, structureGroup: StructureGroup, a return changed } +/** Return a Loci for both directions of the polymer gap element. */ export function getPolymerGapElementLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number) { const { objectId, instanceId, groupId } = pickingId if (id === objectId) { @@ -125,7 +117,10 @@ export function getPolymerGapElementLoci(pickingId: PickingId, structureGroup: S 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(structure, [ Link.Location(unit, unitIndexA, unit, unitIndexB) ]) + return Link.Loci(structure, [ + Link.Location(unit, unitIndexA, unit, unitIndexB), + Link.Location(unit, unitIndexB, unit, unitIndexA) + ]) } } return EmptyLoci