diff --git a/CHANGELOG.md b/CHANGELOG.md index 03a0ba8b8a9d01e71256b231df998e729ef3ac97..73d64332859fff29877d2c48885f3ff40b10ecd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Note that since we don't clearly distinguish between a public and private interf ## [Unreleased] - Add glTF (GLB) and STL support to ``geo-export`` extension. +- Change O-S bond distance to allow for NOS bridges (doi:10.1038/s41586-021-03513-3) ## [v2.0.5] - 2021-04-26 diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts index 2f9110087b83b56a062cacd99d280b57ba6e5f91..f47df8d4336b940bb70989c44367eff716a56586 100644 --- a/src/mol-model/structure/structure/structure.ts +++ b/src/mol-model/structure/structure/structure.ts @@ -34,6 +34,7 @@ import { CustomStructureProperty } from '../../../mol-model-props/common/custom- import { Trajectory } from '../trajectory'; import { RuntimeContext, Task } from '../../../mol-task'; import { computeStructureBoundary } from './util/boundary'; +import { PrincipalAxes } from '../../../mol-math/linear-algebra/matrix/principal-axes'; /** Internal structure state */ type State = { @@ -1290,6 +1291,14 @@ namespace Structure { export type Index = number; export const Index = CustomStructureProperty.createSimple<Index>('index', 'root'); + + const PrincipalAxesProp = '__PrincipalAxes__'; + export function getPrincipalAxes(structure: Structure): PrincipalAxes { + if (structure.currentPropertyData[PrincipalAxesProp]) return structure.currentPropertyData[PrincipalAxesProp]; + const principalAxes = StructureElement.Loci.getPrincipalAxes(Structure.toStructureElementLoci(structure)); + structure.currentPropertyData[PrincipalAxesProp] = principalAxes; + return principalAxes; + } } export { Structure }; \ No newline at end of file diff --git a/src/mol-model/structure/structure/unit/bonds/common.ts b/src/mol-model/structure/structure/unit/bonds/common.ts index 58331ef42f331a2a6404d30aeca9da5e501d51b4..01fa2d49213dde729063c1635290711602f3d49b 100644 --- a/src/mol-model/structure/structure/unit/bonds/common.ts +++ b/src/mol-model/structure/structure/unit/bonds/common.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2017-2020 Mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-2021 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> @@ -23,6 +23,10 @@ const __ElementIndex: { [e: string]: number | undefined } = { 'H': 0, 'h': 0, 'D const __ElementBondThresholds: { [e: number]: number | undefined } = { 0: 1.42, 1: 1.42, 3: 2.7, 4: 2.7, 6: 1.75, 7: 1.6, 8: 1.52, 11: 2.7, 12: 2.7, 13: 2.7, 14: 1.9, 15: 2.0, 16: 1.9, 17: 1.8, 19: 2.7, 20: 2.7, 21: 2.7, 22: 2.7, 23: 2.7, 24: 2.7, 25: 2.7, 26: 2.7, 27: 2.7, 28: 2.7, 29: 2.7, 30: 2.7, 31: 2.7, 33: 2.68, 37: 2.7, 38: 2.7, 39: 2.7, 40: 2.7, 41: 2.7, 42: 2.7, 43: 2.7, 44: 2.7, 45: 2.7, 46: 2.7, 47: 2.7, 48: 2.7, 49: 2.7, 50: 2.7, 55: 2.7, 56: 2.7, 57: 2.7, 58: 2.7, 59: 2.7, 60: 2.7, 61: 2.7, 62: 2.7, 63: 2.7, 64: 2.7, 65: 2.7, 66: 2.7, 67: 2.7, 68: 2.7, 69: 2.7, 70: 2.7, 71: 2.7, 72: 2.7, 73: 2.7, 74: 2.7, 75: 2.7, 76: 2.7, 77: 2.7, 78: 2.7, 79: 2.7, 80: 2.7, 81: 2.7, 82: 2.7, 83: 2.7, 87: 2.7, 88: 2.7, 89: 2.7, 90: 2.7, 91: 2.7, 92: 2.7, 93: 2.7, 94: 2.7, 95: 2.7, 96: 2.7, 97: 2.7, 98: 2.7, 99: 2.7, 100: 2.7, 101: 2.7, 102: 2.7, 103: 2.7, 104: 2.7, 105: 2.7, 106: 2.7, 107: 2.7, 108: 2.7, 109: 2.88 }; /** + * Added O-S (316) as 1.8 + * N-O-S bridge (e.g. LYS-CSO in 7B0L, 6ZWJ, 6ZWH) + * (https://www.nature.com/articles/s41586-021-03513-3?s=09) + * * Increased N-N (112) threshold from 1.55 to 1.6 (e.g. for 0QH in 1BMA) * * More experimentally observed bond length here (https://cccbdb.nist.gov/expbondlengths1x.asp) @@ -51,8 +55,8 @@ const __ElementBondThresholds: { [e: number]: number | undefined } = { 0: 1.42, * Added P-H (135) as 1.47 * P-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rPH) * - Average 1.423 ((+/- 0.007) - * - Min 0.912 - * - Max 1.033 + * - Min 1.414 + * - Max 1.435 * * Added S-H (152) as 1.45 * S-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rSH) @@ -63,7 +67,7 @@ const __ElementBondThresholds: { [e: number]: number | undefined } = { 0: 1.42, * Added Si-Si (420) as 2.37 * (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rSiSi) */ -const __ElementPairThresholds: { [e: number]: number | undefined } = { 0: 0.8, 20: 1.31, 27: 1.2, 35: 1.15, 44: 1.1, 54: 1, 60: 1.84, 72: 1.88, 84: 1.75, 85: 1.56, 86: 1.76, 98: 1.6, 99: 1.68, 100: 1.63, 112: 1.6, 113: 1.59, 114: 1.36, 129: 1.45, 135: 1.47, 144: 1.6, 152: 1.45, 170: 1.4, 180: 1.55, 202: 2.4, 222: 2.24, 224: 1.91, 225: 1.98, 243: 2.02, 269: 2, 293: 1.9, 420: 2.37, 480: 2.3, 512: 2.3, 544: 2.3, 612: 2.1, 629: 1.54, 665: 1, 813: 2.6, 854: 2.27, 894: 1.93, 896: 2.1, 937: 2.05, 938: 2.06, 981: 1.62, 1258: 2.68, 1309: 2.33, 1484: 1, 1763: 2.14, 1823: 2.48, 1882: 2.1, 1944: 1.72, 2380: 2.34, 3367: 2.44, 3733: 2.11, 3819: 2.6, 3821: 2.36, 4736: 2.75, 5724: 2.73, 5959: 2.63, 6519: 2.84, 6750: 2.87, 8991: 2.81 }; +const __ElementPairThresholds: { [e: number]: number | undefined } = { 0: 0.8, 20: 1.31, 27: 1.2, 35: 1.15, 44: 1.1, 54: 1, 60: 1.84, 72: 1.88, 84: 1.75, 85: 1.56, 86: 1.76, 98: 1.6, 99: 1.68, 100: 1.63, 112: 1.6, 113: 1.59, 114: 1.36, 129: 1.45, 135: 1.47, 144: 1.6, 152: 1.45, 170: 1.4, 180: 1.55, 202: 2.4, 222: 2.24, 224: 1.91, 225: 1.98, 243: 2.02, 269: 2, 293: 1.9, 316: 1.8, 420: 2.37, 480: 2.3, 512: 2.3, 544: 2.3, 612: 2.1, 629: 1.54, 665: 1, 813: 2.6, 854: 2.27, 894: 1.93, 896: 2.1, 937: 2.05, 938: 2.06, 981: 1.62, 1258: 2.68, 1309: 2.33, 1484: 1, 1763: 2.14, 1823: 2.48, 1882: 2.1, 1944: 1.72, 2380: 2.34, 3367: 2.44, 3733: 2.11, 3819: 2.6, 3821: 2.36, 4736: 2.75, 5724: 2.73, 5959: 2.63, 6519: 2.84, 6750: 2.87, 8991: 2.81 }; const __DefaultBondingRadius = 2.001; diff --git a/src/mol-plugin-state/animation/built-in/spin-structure.ts b/src/mol-plugin-state/animation/built-in/spin-structure.ts new file mode 100644 index 0000000000000000000000000000000000000000..2fd9d64ba9c0021e142390835b39bcfaec046265 --- /dev/null +++ b/src/mol-plugin-state/animation/built-in/spin-structure.ts @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { PluginCommands } from '../../../mol-plugin/commands'; +import { StateSelection } from '../../../mol-state'; +import { ParamDefinition as PD } from '../../../mol-util/param-definition'; +import { PluginStateObject } from '../../objects'; +import { StateTransforms } from '../../transforms'; +import { PluginStateAnimation } from '../model'; + +export const AnimateStructureSpin = PluginStateAnimation.create({ + name: 'built-in.animate-structure-spin', + display: { name: 'Spin Structure' }, + isExportable: true, + params: () => ({ + durationInMs: PD.Numeric(3000, { min: 100, max: 10000, step: 100}) + }), + initialState: () => ({ t: 0 }), + getDuration: p => ({ kind: 'fixed', durationMs: p.durationInMs }), + async setup(_, __, plugin) { + const state = plugin.state.data; + const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3D)); + + const update = state.build(); + let changed = false; + for (const r of reprs) { + const spins = state.select(StateSelection.Generators.ofTransformer(StateTransforms.Representation.SpinStructureRepresentation3D, r.transform.ref)); + if (spins.length > 0) continue; + + changed = true; + update.to(r.transform.ref) + .apply(StateTransforms.Representation.SpinStructureRepresentation3D, { t: 0 }, { tags: 'animate-structure-spin' }); + } + + if (!changed) return; + + return update.commit({ doNotUpdateCurrent: true }); + }, + teardown(_, __, plugin) { + const state = plugin.state.data; + const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3DState) + .withTag('animate-structure-spin')); + if (reprs.length === 0) return; + + const update = state.build(); + for (const r of reprs) update.delete(r.transform.ref); + return update.commit(); + }, + async apply(animState, t, ctx) { + const state = ctx.plugin.state.data; + const anims = state.select(StateSelection.Generators.ofTransformer(StateTransforms.Representation.SpinStructureRepresentation3D)); + + if (anims.length === 0) { + return { kind: 'finished' }; + } + + const update = state.build(); + + const d = (t.current - t.lastApplied) / ctx.params.durationInMs; + const newTime = (animState.t + d) % 1; + + for (const m of anims) { + update.to(m).update({ ...m.params?.values, t: newTime }); + } + + await PluginCommands.State.Update(ctx.plugin, { state, tree: update, options: { doNotLogTiming: true } }); + + return { kind: 'next', state: { t: newTime } }; + } +}); \ No newline at end of file diff --git a/src/mol-plugin-state/animation/helpers.ts b/src/mol-plugin-state/animation/helpers.ts index 700eef0f7cec7b3eb5b1eb464657ca98aec6893d..b3e554b1b26d8483452fbfa46befaa874c3d0ae9 100644 --- a/src/mol-plugin-state/animation/helpers.ts +++ b/src/mol-plugin-state/animation/helpers.ts @@ -1,9 +1,11 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2021 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> */ +import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { SymmetryOperator } from '../../mol-math/geometry'; import { Mat4, Vec3 } from '../../mol-math/linear-algebra'; import { Structure } from '../../mol-model/structure'; @@ -32,4 +34,60 @@ export function explodeStructure(structure: Structure, unitTransforms: Structure unitTransforms.setTransform(_transMat, u); } +} + +// + +export const SpinStructureParams = { + axis: PD.MappedStatic('custom', { + structure: PD.Group({ + principalAxis: PD.Select('dirA', [['dirA', 'A'], ['dirB', 'B'], ['dirC', 'C']] as const) + }), + custom: PD.Group({ + vector: PD.Vec3(Vec3.create(0, 0, 1)) + }) + }), + origin: PD.MappedStatic('structure', { + structure: PD.Group({}), + custom: PD.Group({ + vector: PD.Vec3(Vec3.create(0, 0, 0)) + }) + }), +}; +export type SpinStructureProps = PD.Values<typeof SpinStructureParams> + +export function getSpinStructureAxisAndOrigin(structure: Structure, props: SpinStructureProps) { + let axis: Vec3, origin: Vec3; + + if (props.axis.name === 'custom') { + axis = props.axis.params.vector; + } else { + const pa = Structure.getPrincipalAxes(structure); + axis = pa.momentsAxes[props.axis.params.principalAxis]; + } + + if (props.origin.name === 'custom') { + origin = props.origin.params.vector; + } else { + const pa = Structure.getPrincipalAxes(structure); + origin = pa.momentsAxes.origin; + } + + return { axis, origin }; +} + +const _rotMat = Mat4(); +const _transMat2 = Mat4(); +const _t = Mat4(); +export function spinStructure(structure: Structure, unitTransforms: StructureUnitTransforms, t: number, axis: Vec3, origin: Vec3) { + for (let i = 0, _i = structure.units.length; i < _i; i++) { + const u = structure.units[i]; + Vec3.negate(_transVec, origin); + Mat4.fromTranslation(_transMat, _transVec); + Mat4.fromRotation(_rotMat, Math.PI * t * 2, axis); + Mat4.fromTranslation(_transMat2, origin); + Mat4.mul(_t, _rotMat, _transMat); + Mat4.mul(_t, _transMat2, _t); + unitTransforms.setTransform(_t, u); + } } \ No newline at end of file diff --git a/src/mol-plugin-state/transforms/representation.ts b/src/mol-plugin-state/transforms/representation.ts index 4e726697afacd3d3d788b1e6fd10f8195fde1c12..d4bb3e506bb40e988733dd96cfa111f1f4feb54e 100644 --- a/src/mol-plugin-state/transforms/representation.ts +++ b/src/mol-plugin-state/transforms/representation.ts @@ -20,7 +20,7 @@ import { PluginStateObject as SO, PluginStateTransform } from '../objects'; import { ColorNames } from '../../mol-util/color/names'; import { ShapeRepresentation } from '../../mol-repr/shape/representation'; import { StructureUnitTransforms } from '../../mol-model/structure/structure/util/unit-transforms'; -import { unwindStructureAssembly, explodeStructure } from '../animation/helpers'; +import { unwindStructureAssembly, explodeStructure, spinStructure, SpinStructureParams, getSpinStructureAxisAndOrigin } from '../animation/helpers'; import { Color } from '../../mol-util/color'; import { Overpaint } from '../../mol-theme/overpaint'; import { Transparency } from '../../mol-theme/transparency'; @@ -43,6 +43,7 @@ import { Box3D } from '../../mol-math/geometry'; export { StructureRepresentation3D }; export { ExplodeStructureRepresentation3D }; +export { SpinStructureRepresentation3D }; export { UnwindStructureAssemblyRepresentation3D }; export { OverpaintStructureRepresentation3DFromScript }; export { OverpaintStructureRepresentation3DFromBundle }; @@ -222,7 +223,6 @@ const UnwindStructureAssemblyRepresentation3D = PluginStateTransform.BuiltIn({ } }); - type ExplodeStructureRepresentation3D = typeof ExplodeStructureRepresentation3D const ExplodeStructureRepresentation3D = PluginStateTransform.BuiltIn({ name: 'explode-structure-representation-3d', @@ -259,6 +259,49 @@ const ExplodeStructureRepresentation3D = PluginStateTransform.BuiltIn({ } }); +type SpinStructureRepresentation3D = typeof SpinStructureRepresentation3D +const SpinStructureRepresentation3D = PluginStateTransform.BuiltIn({ + name: 'spin-structure-representation-3d', + display: 'Spin 3D Representation', + from: SO.Molecule.Structure.Representation3D, + to: SO.Molecule.Structure.Representation3DState, + params: { + t: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }), + ...SpinStructureParams + } +})({ + canAutoUpdate() { + return true; + }, + apply({ a, params }) { + const structure = a.data.sourceData; + const unitTransforms = new StructureUnitTransforms(structure.root); + + const { axis, origin } = getSpinStructureAxisAndOrigin(structure.root, params); + spinStructure(structure, unitTransforms, params.t, axis, origin); + return new SO.Molecule.Structure.Representation3DState({ + state: { unitTransforms }, + initialState: { unitTransforms: new StructureUnitTransforms(structure.root) }, + info: structure.root, + repr: a.data.repr + }, { label: `Spin T = ${params.t.toFixed(2)}` }); + }, + update({ a, b, newParams, oldParams }) { + const structure = a.data.sourceData; + if (b.data.info !== structure.root) return StateTransformer.UpdateResult.Recreate; + if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate; + + if (oldParams.t === newParams.t && oldParams.axis === newParams.axis && oldParams.origin === newParams.origin) return StateTransformer.UpdateResult.Unchanged; + + const unitTransforms = b.data.state.unitTransforms!; + const { axis, origin } = getSpinStructureAxisAndOrigin(structure.root, newParams); + spinStructure(structure.root, unitTransforms, newParams.t, axis, origin); + b.label = `Spin T = ${newParams.t.toFixed(2)}`; + b.data.repr = a.data.repr; + return StateTransformer.UpdateResult.Updated; + } +}); + type OverpaintStructureRepresentation3DFromScript = typeof OverpaintStructureRepresentation3DFromScript const OverpaintStructureRepresentation3DFromScript = PluginStateTransform.BuiltIn({ name: 'overpaint-structure-representation-3d-from-script', diff --git a/src/mol-plugin/spec.ts b/src/mol-plugin/spec.ts index d9cca1117df7f8a1fd57aed01baa160820db42de..1438a1cfc6bdfe21bb4be97575d208d6301894c4 100644 --- a/src/mol-plugin/spec.ts +++ b/src/mol-plugin/spec.ts @@ -22,6 +22,7 @@ import { AssignColorVolume } from '../mol-plugin-state/actions/volume'; import { StateTransforms } from '../mol-plugin-state/transforms'; import { BoxifyVolumeStreaming, CreateVolumeStreamingBehavior, InitVolumeStreaming } from '../mol-plugin/behavior/dynamic/volume-streaming/transformers'; import { AnimateStateInterpolation } from '../mol-plugin-state/animation/built-in/state-interpolation'; +import { AnimateStructureSpin } from '../mol-plugin-state/animation/built-in/spin-structure'; export { PluginSpec }; @@ -96,6 +97,7 @@ export const DefaultPluginSpec = (): PluginSpec => ({ PluginSpec.Action(StateTransforms.Representation.ModelUnitcell3D), PluginSpec.Action(StateTransforms.Representation.StructureBoundingBox3D), PluginSpec.Action(StateTransforms.Representation.ExplodeStructureRepresentation3D), + PluginSpec.Action(StateTransforms.Representation.SpinStructureRepresentation3D), PluginSpec.Action(StateTransforms.Representation.UnwindStructureAssemblyRepresentation3D), PluginSpec.Action(StateTransforms.Representation.OverpaintStructureRepresentation3DFromScript), PluginSpec.Action(StateTransforms.Representation.TransparencyStructureRepresentation3DFromScript), @@ -128,6 +130,7 @@ export const DefaultPluginSpec = (): PluginSpec => ({ AnimateCameraSpin, AnimateStateSnapshots, AnimateAssemblyUnwind, + AnimateStructureSpin, AnimateStateInterpolation ] }); \ No newline at end of file