Browse Source

fix visuals for aromatic/delocalized bonds

Alexander Rose 3 years ago
parent
commit
105f6c3041

+ 2 - 0
CHANGELOG.md

@@ -9,6 +9,8 @@ Note that since we don't clearly distinguish between a public and private interf
 - Fix parsing contour-level from emdb v3 header files
 - Fix invalid CSS (#376)
 - Fix "texture not renderable" & "texture not bound" warnings (#319)
+- Fix visual for bonds between two aromatic rings
+- Fix visual for delocalized bonds (parsed from mmcif and mol2)
 
 ## [v3.2.0] - 2022-02-17
 

+ 1 - 1
src/mol-model-formats/structure/mol2.ts

@@ -103,11 +103,11 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
             const flag = Column.ofIntArray(Column.mapToArray(bonds.bond_type, x => {
                 switch (x) {
                     case 'ar': // aromatic
+                    case 'am': // amide
                         return BondType.Flag.Aromatic | BondType.Flag.Covalent;
                     case 'du': // dummy
                     case 'nc': // not connected
                         return BondType.Flag.None;
-                    case 'am': // amide
                     case 'un': // unknown
                     default:
                         return BondType.Flag.Covalent;

+ 7 - 9
src/mol-model-formats/structure/property/bonds/chem_comp.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2020 Mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2022 Mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -56,10 +56,10 @@ export namespace ComponentBond {
         const entries: Map<string, Entry> = new Map();
 
         function addEntry(id: string) {
-            // weird behavior when 'PRO' is requested - will report a single bond between N and H because a later operation would override real content
-            if (entries.has(id)) {
-                return entries.get(id)!;
-            }
+            // weird behavior when 'PRO' is requested - will report a single bond
+            // between N and H because a later operation would override real content
+            if (entries.has(id)) return entries.get(id)!;
+
             const e = new Entry(id);
             entries.set(id, e);
             return e;
@@ -83,10 +83,8 @@ export namespace ComponentBond {
             let ord = 1;
             if (aromatic) flags |= BondType.Flag.Aromatic;
             switch (order.toLowerCase()) {
-                case 'doub':
-                case 'delo':
-                    ord = 2;
-                    break;
+                case 'delo': flags |= BondType.Flag.Aromatic; break;
+                case 'doub': ord = 2; break;
                 case 'trip': ord = 3; break;
                 case 'quad': ord = 4; break;
             }

+ 13 - 3
src/mol-repr/structure/visual/bond-intra-unit-cylinder.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 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>
@@ -16,7 +16,7 @@ import { createLinkCylinderImpostors, createLinkCylinderMesh, LinkBuilderProps,
 import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, UnitsCylindersParams, UnitsCylindersVisual } from '../units-visual';
 import { VisualUpdateState } from '../../util';
 import { BondType } from '../../../mol-model/structure/model/types';
-import { BondCylinderParams, BondIterator, eachIntraBond, getIntraBondLoci, ignoreBondType, makeIntraBondIgnoreTest } from './util/bond';
+import { BondCylinderParams, BondIterator, eachIntraBond, getDelocalizedTriplets, getIntraBondLoci, ignoreBondType, makeIntraBondIgnoreTest } from './util/bond';
 import { Sphere3D } from '../../../mol-math/geometry';
 import { IntAdjacencyGraph } from '../../../mol-math/graph';
 import { WebGLContext } from '../../../mol-gl/webgl/context';
@@ -79,12 +79,20 @@ function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Stru
     };
 
     const { elementRingIndices, elementAromaticRingIndices } = unit.rings;
+    const deloTriplets = aromaticBonds ? getDelocalizedTriplets(unit) : undefined;
 
     return {
         linkCount: edgeCount * 2,
         referencePosition: (edgeIndex: number) => {
             let aI = a[edgeIndex], bI = b[edgeIndex];
 
+            if (deloTriplets) {
+                const rI = deloTriplets.get(aI, bI);
+                if (rI !== undefined) {
+                    return pos(elements[rI], vRef);
+                }
+            }
+
             if (aI > bI) [aI, bI] = [bI, aI];
             if (offset[aI + 1] - offset[aI] === 1) [aI, bI] = [bI, aI];
 
@@ -145,8 +153,10 @@ function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Stru
                 if (isBondType(f, BondType.Flag.Aromatic) || (arCount && !ignoreComputedAromatic)) {
                     if (arCount === 2) {
                         return LinkStyle.MirroredAromatic;
-                    } else {
+                    } else if (arCount === 1 || deloTriplets?.get(aI, bI)) {
                         return LinkStyle.Aromatic;
+                    } else {
+                        // case for bonds between two aromatic rings
                     }
                 }
             }

+ 13 - 3
src/mol-repr/structure/visual/bond-intra-unit-line.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -14,7 +14,7 @@ import { LinkStyle, createLinkLines, LinkBuilderProps } from './util/link';
 import { UnitsVisual, UnitsLinesParams, UnitsLinesVisual } from '../units-visual';
 import { VisualUpdateState } from '../../util';
 import { BondType } from '../../../mol-model/structure/model/types';
-import { BondIterator, BondLineParams, getIntraBondLoci, eachIntraBond, makeIntraBondIgnoreTest, ignoreBondType } from './util/bond';
+import { BondIterator, BondLineParams, getIntraBondLoci, eachIntraBond, makeIntraBondIgnoreTest, ignoreBondType, getDelocalizedTriplets } from './util/bond';
 import { Sphere3D } from '../../../mol-math/geometry';
 import { Lines } from '../../../mol-geo/geometry/lines/lines';
 import { IntAdjacencyGraph } from '../../../mol-math/graph';
@@ -52,12 +52,20 @@ function createIntraUnitBondLines(ctx: VisualContext, unit: Unit, structure: Str
     const pos = unit.conformation.invariantPosition;
 
     const { elementRingIndices, elementAromaticRingIndices } = unit.rings;
+    const deloTriplets = aromaticBonds ? getDelocalizedTriplets(unit) : undefined;
 
     const builderProps: LinkBuilderProps = {
         linkCount: edgeCount * 2,
         referencePosition: (edgeIndex: number) => {
             let aI = a[edgeIndex], bI = b[edgeIndex];
 
+            if (deloTriplets) {
+                const rI = deloTriplets.get(aI, bI);
+                if (rI !== undefined) {
+                    return pos(elements[rI], vRef);
+                }
+            }
+
             if (aI > bI) [aI, bI] = [bI, aI];
             if (offset[aI + 1] - offset[aI] === 1) [aI, bI] = [bI, aI];
 
@@ -106,8 +114,10 @@ function createIntraUnitBondLines(ctx: VisualContext, unit: Unit, structure: Str
                 if (isBondType(f, BondType.Flag.Aromatic) || (arCount && !ignoreComputedAromatic)) {
                     if (arCount === 2) {
                         return LinkStyle.MirroredAromatic;
-                    } else {
+                    } else if (arCount === 1 || deloTriplets?.get(aI, bI)) {
                         return LinkStyle.Aromatic;
+                    } else {
+                        // case for bonds between two aromatic rings
                     }
                 }
             }

+ 46 - 0
src/mol-repr/structure/visual/util/bond.ts

@@ -14,6 +14,7 @@ import { PickingId } from '../../../../mol-geo/geometry/picking';
 import { EmptyLoci, Loci } from '../../../../mol-model/loci';
 import { Interval, OrderedSet, SortedArray } from '../../../../mol-data/int';
 import { isH, isHydrogen, StructureGroup } from './common';
+import { sortedCantorPairing } from '../../../../mol-data/util';
 
 export const BondParams = {
     includeTypes: PD.MultiSelect(ObjectKeys(BondType.Names), PD.objectToOptions(BondType.Names)),
@@ -264,4 +265,49 @@ export function eachInterBond(loci: Loci, structure: Structure, apply: (interval
         __unitMap.clear();
     }
     return changed;
+}
+
+//
+
+/**
+ * Get a lookup for triplets of atoms in delocalized bonds.
+ *
+ * Does not include triplets that are part of aromatic rings
+ */
+export function getDelocalizedTriplets(unit: Unit.Atomic) {
+    const bonds = unit.bonds;
+    const { b, edgeProps, offset } = bonds;
+    const { order: _order, flags: _flags } = edgeProps;
+    const { elementAromaticRingIndices } = unit.rings;
+
+    const map = new Map<number, StructureElement.UnitIndex>();
+
+    for (let i = 0 as StructureElement.UnitIndex; i < unit.elements.length; i++) {
+        if (elementAromaticRingIndices.has(i)) continue;
+
+        const count = offset[i + 1] - offset[i] + 1;
+        if (count < 2) continue;
+
+        const deloBonds: StructureElement.UnitIndex[] = [];
+        for (let t = offset[i], _t = offset[i + 1]; t < _t; t++) {
+            const f = _flags[t];
+            if (!BondType.is(f, BondType.Flag.Aromatic)) continue;
+
+            deloBonds.push(b[t]);
+        }
+
+        if (deloBonds.length >= 2) {
+            map.set(sortedCantorPairing(i, deloBonds[0]), deloBonds[1]);
+            for (let j = 1, jl = deloBonds.length; j < jl; j++) {
+                map.set(sortedCantorPairing(i, deloBonds[j]), deloBonds[0]);
+            }
+        }
+    }
+
+    return {
+        /** Return 3rd element in triplet or undefined if `a` and `b` are not part of a triplet */
+        get: (a: StructureElement.UnitIndex, b: StructureElement.UnitIndex) => {
+            return map.get(sortedCantorPairing(a, b));
+        }
+    };
 }