generalized support of interaction bridges

This commit is contained in:
Sebastian
2026-05-26 16:06:14 +02:00
parent 18ad848de2
commit 340806d774
4 changed files with 43 additions and 22 deletions

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2026 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>
@@ -15,7 +15,7 @@ import { IntMap } from '../../../mol-data/int';
import { addUnitContacts, ContactTester, addStructureContacts, ContactsParams, ContactsProps } from './contacts';
import { HalogenDonorProvider, HalogenAcceptorProvider, HalogenBondsProvider } from './halogen-bonds';
import { HydrogenDonorProvider, WeakHydrogenDonorProvider, HydrogenAcceptorProvider, HydrogenBondsProvider, WeakHydrogenBondsProvider } from './hydrogen-bonds';
import { findWaterBridgeContacts, WaterBridgeContacts, WaterBridgesParams } from './water-bridges';
import { findWaterBridgeContacts, WaterBridgesParams, WaterBridgeContact } from './water-bridges';
import { NegativChargeProvider, PositiveChargeProvider, AromaticRingProvider, IonicProvider, PiStackingProvider, CationPiProvider } from './charged';
import { HydrophobicAtomProvider, HydrophobicProvider } from './hydrophobic';
import { SetUtils } from '../../../mol-util/set';
@@ -30,6 +30,10 @@ import { bondLabel, LabelGranularity } from '../../../mol-theme/label';
import { ObjectKeys } from '../../../mol-util/type-helpers';
export { Interactions };
export type { BridgeContact, BridgeContacts };
type BridgeContact = WaterBridgeContact
type BridgeContacts = ReadonlyArray<BridgeContact>
interface Interactions {
/** Features of each unit */
@@ -38,8 +42,8 @@ interface Interactions {
unitsContacts: IntMap<InteractionsIntraContacts>
/** Interactions between units */
contacts: InteractionsInterContacts
/** Water-mediated hydrogen bonds (donor → water → acceptor) */
waterBridges: WaterBridgeContacts
/** Bridge-mediated interactions covering the whole structure */
bridges: BridgeContacts
}
namespace Interactions {
@@ -239,13 +243,9 @@ export async function computeInteractions(ctx: CustomProperty.Context, structure
}
const contacts = findInterUnitContacts(structure, unitsFeatures, contactTesters, p.contacts, options);
const bridges = findBridges(structure, unitsFeatures, p.waterBridges);
const interactions = { unitsFeatures, unitsContacts, contacts, bridges };
const wbToggle = p.waterBridges['water-bridges'];
const waterBridges = wbToggle.name === 'on'
? findWaterBridgeContacts(structure, unitsFeatures, wbToggle.params)
: [];
const interactions = { unitsFeatures, unitsContacts, contacts, waterBridges };
refineInteractions(structure, interactions);
return interactions;
}
@@ -276,6 +276,17 @@ function findIntraUnitContacts(structure: Structure, unit: Unit, features: Featu
return builder.getContacts();
}
function findBridges(structure: Structure, unitsFeatures: IntMap<Features>, props: PD.Values<typeof WaterBridgesToggleParams>): BridgeContacts {
const bridges: BridgeContact[] = [];
const wb = props['water-bridges'];
if (wb.name === 'on') {
for (const b of findWaterBridgeContacts(structure, unitsFeatures, wb.params)) bridges.push(b);
}
return bridges;
}
function findInterUnitContacts(structure: Structure, unitsFeatures: IntMap<Features>, contactTesters: ReadonlyArray<ContactTester>, props: ContactsProps, options?: ComputeInterctionsOptions) {
const builder = InterContactsBuilder.create();

View File

@@ -284,7 +284,7 @@ function metalCoordinationRefiner(structure: Structure, interactions: Interactio
}
function waterBridgeRefiner(_structure: Structure, interactions: Interactions): ContactRefiner {
const { contacts, waterBridges, unitsFeatures } = interactions;
const { contacts, bridges, unitsFeatures } = interactions;
type AtomKey = number;
type AtomPairSet = Map<AtomKey, Set<AtomKey>>;
@@ -351,7 +351,9 @@ function waterBridgeRefiner(_structure: Structure, interactions: Interactions):
const bridgeLegs: AtomPairSet = new Map();
for (const wb of waterBridges) {
for (const wb of bridges) {
if (wb.props.type !== InteractionType.WaterBridge) continue;
const fA = unitsFeatures.get(wb.unitA);
const fW = unitsFeatures.get(wb.unitW);
const fB = unitsFeatures.get(wb.unitB);

View File

@@ -15,7 +15,7 @@ import { StructureLookup3DResultContext } from '../../../mol-model/structure/str
import { Sphere3D } from '../../../mol-math/geometry';
import { CentroidHelper } from '../../../mol-math/geometry/centroid-helper';
import { Features } from './features';
import { FeatureType } from './common';
import { FeatureType, InteractionType, InteractionFlag } from './common';
import { GeometryOptions, checkGeometry } from './hydrogen-bonds';
import { degToRad } from '../../../mol-math/misc';
import { bundleLabel, LabelGranularity } from '../../../mol-theme/label';
@@ -38,6 +38,7 @@ interface WaterBridgeContact {
readonly indexWA: Features.FeatureIndex
/** water oxygen as HydrogenDonor (leg: water → acceptor) */
readonly indexWD: Features.FeatureIndex
props: { type: InteractionType.WaterBridge, flag: InteractionFlag }
}
type WaterBridgeContacts = ReadonlyArray<WaterBridgeContact>;
@@ -315,6 +316,7 @@ export function findWaterBridgeContacts(
unitW: unitW.id,
indexWA: accFW,
indexWD: donFW,
props: { type: InteractionType.WaterBridge, flag: InteractionFlag.None },
},
combinedDistSq,
});

View File

@@ -19,7 +19,8 @@ import { NullLocation } from '../../../mol-model/location';
import { Interval, OrderedSet } from '../../../mol-data/int';
import { InteractionsProvider } from '../interactions';
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
import { WaterBridges } from '../interactions/water-bridges';
import { WaterBridges, WaterBridgeContact } from '../interactions/water-bridges';
import { InteractionType } from '../interactions/common';
import { Sphere3D } from '../../../mol-math/geometry';
import { InteractionsSharedParams } from './shared';
import { Features } from '../interactions/features';
@@ -129,14 +130,15 @@ function createWaterBridgeCylinderMesh(ctx: VisualContext, structure: Structure,
const interactions = InteractionsProvider.get(structure).value;
if (!interactions) return Mesh.createEmpty(mesh);
const { waterBridges, unitsFeatures } = interactions;
const { bridges, unitsFeatures } = interactions;
const waterBridges = bridges.filter((b): b is WaterBridgeContact => b.props.type === InteractionType.WaterBridge);
const n = waterBridges.length;
if (!n) return Mesh.createEmpty(mesh);
const l = StructureElement.Location.create(structure);
const { sizeFactor } = props;
const canonical = getCanonicalLegIndices(waterBridges);
const canonical = getCanonicalLegIndices(bridges);
const builderProps = {
// Four half-cylinders per bridge; createLinkCylinderMesh draws the A-side half per call:
@@ -288,14 +290,15 @@ function getWaterBridgeLoci(pickingId: PickingId, structure: Structure, id: numb
const interactions = InteractionsProvider.get(structure).value;
if (!interactions) return EmptyLoci;
const { waterBridges, unitsFeatures } = interactions;
const { bridges, unitsFeatures } = interactions;
const waterBridges = bridges.filter((b): b is WaterBridgeContact => b.props.type === InteractionType.WaterBridge);
const n = waterBridges.length;
if (!n || groupId < 0 || groupId >= 4 * n) return EmptyLoci;
const bridgeIndex = groupId % n;
return WaterBridges.Loci({ structure, waterBridges, unitsFeatures }, [{ bridgeIndex }]);
return WaterBridges.Loci({ structure, waterBridges: bridges, unitsFeatures }, [{ bridgeIndex }]);
}
const __unitMap = new Map<number, OrderedSet<StructureElement.UnitIndex>>();
@@ -309,10 +312,11 @@ function eachWaterBridgeInteraction(loci: Loci, structure: Structure, apply: (in
const interactions = InteractionsProvider.get(structure).value;
if (!interactions) return false;
const n = interactions.waterBridges.length;
const waterBridges = interactions.bridges.filter((b): b is WaterBridgeContact => b.props.type === InteractionType.WaterBridge);
const n = waterBridges.length;
if (!n) return false;
const canonical = getCanonicalLegIndices(interactions.waterBridges);
const canonical = getCanonicalLegIndices(waterBridges);
for (const e of loci.elements) {
if (e.bridgeIndex < 0 || e.bridgeIndex >= n) continue;
@@ -326,7 +330,8 @@ function eachWaterBridgeInteraction(loci: Loci, structure: Structure, apply: (in
const interactions = InteractionsProvider.get(structure).value;
if (!interactions) return false;
const { waterBridges, unitsFeatures } = interactions;
const { bridges, unitsFeatures } = interactions;
const waterBridges = bridges.filter((b): b is WaterBridgeContact => b.props.type === InteractionType.WaterBridge);
const n = waterBridges.length;
if (!n) return false;
@@ -387,7 +392,8 @@ function createWaterBridgeIterator(structure: Structure): LocationIterator {
const interactions = InteractionsProvider.get(structure).value;
if (!interactions) return LocationIterator(0, 1, 1, () => NullLocation, true);
const { waterBridges, unitsFeatures } = interactions;
const { bridges, unitsFeatures } = interactions;
const waterBridges = bridges.filter((b): b is WaterBridgeContact => b.props.type === InteractionType.WaterBridge);
const n = waterBridges.length;
const groupCount = 4 * n;