UNPKG

molstar

Version:

A comprehensive macromolecular library.

176 lines (175 loc) 7.92 kB
/** * Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> */ import { addFixedCountDashedCylinder, addSimpleCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder'; import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder'; import { Sphere3D } from '../../mol-math/geometry'; import { Vec3 } from '../../mol-math/linear-algebra'; import { Shape } from '../../mol-model/shape'; import { StructureElement } from '../../mol-model/structure'; import { addLinkCylinderMesh, DefaultLinkCylinderProps } from '../../mol-repr/structure/visual/util/link'; import { Color } from '../../mol-util/color'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { stringToWords } from '../../mol-util/string'; import { InteractionKinds } from './model'; function visualParams({ color, style = 'dashed', radius = 0.04 }) { return PD.Group({ color: PD.Color(color), style: PD.Select(style, [['dashed', 'Dashed'], ['solid', 'Solid']]), radius: PD.Numeric(radius, { min: 0.01, max: 1, step: 0.01 }), }); } function hydrogenVisualParams({ color, style = 'dashed', radius = 0.04, showArrow = true, arrowOffset = 0.18 }) { return PD.Group({ color: PD.Color(color), style: PD.Select(style, [['dashed', 'Dashed'], ['solid', 'Solid']]), radius: PD.Numeric(radius, { min: 0.01, max: 1, step: 0.01 }), showArrow: PD.Boolean(showArrow), arrowOffset: PD.Numeric(arrowOffset, { min: 0, max: 1, step: 0.001 }), }); } export const InteractionVisualParams = { kinds: PD.MultiSelect(InteractionKinds, InteractionKinds.map(k => [k, stringToWords(k)])), styles: PD.Group({ 'unknown': visualParams({ color: Color(0x0) }), 'ionic': visualParams({ color: Color(0xADD8E6) }), 'pi-stacking': visualParams({ color: Color(0x1E3F66) }), 'cation-pi': visualParams({ color: Color(0x06402B) }), 'halogen-bond': visualParams({ color: Color(0xFFDE21) }), 'hydrogen-bond': hydrogenVisualParams({ color: Color(0x0), style: 'solid' }), 'weak-hydrogen-bond': hydrogenVisualParams({ color: Color(0x0) }), 'hydrophobic': visualParams({ color: Color(0x555555) }), 'metal-coordination': visualParams({ color: Color(0x952e8f) }), 'covalent': PD.Group({ color: PD.Color(Color(0x999999)), radius: PD.Numeric(0.1, { min: 0.01, max: 1, step: 0.01 }), }), }) }; export function buildInteractionsShape(interactions, params, prev) { var _a, _b, _c; const mesh = MeshBuilder.createState(interactions.elements.length * 128, 1024, prev); mesh.currentGroup = -1; const tooltips = new Map(); const visible = new Set(params.kinds); const kindsToWords = new Map(InteractionKinds.map(k => [k, stringToWords(k)])); const colors = new Map(); const bA = { sphere: Sphere3D.zero() }; const bB = { sphere: Sphere3D.zero() }; const pA = Vec3(); const pB = Vec3(); const dir = Vec3(); const capPos = Vec3(); const addLinkOptions = { builderState: mesh, props: { ...DefaultLinkCylinderProps }, }; const addLinkParams = { a: pA, b: pB, group: 0, linkStub: false, linkStyle: 0 /* LinkStyle.Solid */, linkRadius: 0, }; for (const interaction of interactions.elements) { mesh.currentGroup++; if (!visible.has(interaction.info.kind)) continue; let tooltip; if (interaction.info.kind === 'covalent') { if (interaction.info.degree === 'aromatic') tooltip = 'Aromatic'; else if (interaction.info.degree === 1) tooltip = 'Single'; else if (interaction.info.degree === 2) tooltip = 'Double'; else if (interaction.info.degree === 3) tooltip = 'Triple'; else if (interaction.info.degree === 4) tooltip = 'Quadruple'; else tooltip = 'Covalent'; } else { tooltip = (_a = kindsToWords.get(interaction.info.kind)) !== null && _a !== void 0 ? _a : interaction.info.kind; } if ((_b = interaction.sourceSchema) === null || _b === void 0 ? void 0 : _b.description) { tooltip += ` (${interaction.sourceSchema.description})`; } tooltips.set(mesh.currentGroup, tooltip); const options = params.styles[interaction.info.kind]; let style = 'solid'; if (interaction.info.kind !== 'covalent') { style = params.styles[interaction.info.kind].style; } colors.set(mesh.currentGroup, params.styles[interaction.info.kind].color); StructureElement.Loci.getBoundary(interaction.a, undefined, bA); StructureElement.Loci.getBoundary(interaction.b, undefined, bB); Vec3.sub(dir, bB.sphere.center, bA.sphere.center); Vec3.normalize(dir, dir); Vec3.copy(pA, bA.sphere.center); Vec3.copy(pB, bB.sphere.center); if (interaction.info.kind === 'hydrogen-bond' || interaction.info.kind === 'weak-hydrogen-bond') { const hydrogenStyle = params.styles[interaction.info.kind]; if (hydrogenStyle.showArrow && hydrogenStyle.arrowOffset > 0) { Vec3.scaleAndAdd(pB, pB, dir, -hydrogenStyle.arrowOffset); } if (hydrogenStyle.showArrow) { const height = options.radius * 3; Vec3.scaleAndAdd(capPos, pB, dir, -height); cylinder(mesh, pA, capPos, options.radius, style); addSimpleCylinder(mesh, capPos, pB, { radiusTop: 0, radiusBottom: height, topCap: false, bottomCap: true }); } else { cylinder(mesh, pA, pB, options.radius, style); } } else { if (interaction.info.kind !== 'covalent') { cylinder(mesh, pA, pB, options.radius, style); } else { addLinkParams.group = mesh.currentGroup; addLinkParams.linkRadius = options.radius; const degree = (_c = interaction.info.degree) !== null && _c !== void 0 ? _c : 1; if (degree === 'aromatic') addLinkParams.linkStyle = 7 /* LinkStyle.Aromatic */; else if (degree === 2) addLinkParams.linkStyle = 2 /* LinkStyle.Double */; else if (degree === 3) addLinkParams.linkStyle = 4 /* LinkStyle.Triple */; else addLinkParams.linkStyle = 0 /* LinkStyle.Solid */; addLinkParams.a = pA; addLinkParams.b = pB; addLinkCylinderMesh(addLinkOptions, addLinkParams); addLinkParams.a = pB; addLinkParams.b = pA; addLinkCylinderMesh(addLinkOptions, addLinkParams); } } } return Shape.create('Interactions', interactions, MeshBuilder.getMesh(mesh), (g) => { var _a; return (_a = colors.get(g)) !== null && _a !== void 0 ? _a : 0; }, (g) => 1, (g) => { var _a; return (_a = tooltips.get(g)) !== null && _a !== void 0 ? _a : ''; }); } function cylinder(mesh, a, b, radius, style) { const props = { radiusBottom: radius, radiusTop: radius, topCap: true, bottomCap: true, }; if (style === 'dashed') { const dist = Vec3.distance(a, b); const count = Math.ceil(dist / (2 * radius)); addFixedCountDashedCylinder(mesh, a, b, 1.0, count, true, props); } else { if (style !== 'solid') { console.warn(`Unknown style '${style}', using 'solid' instead.`); } addSimpleCylinder(mesh, a, b, props); } }