diff --git a/src/mol-model-formats/structure/mmcif.ts b/src/mol-model-formats/structure/mmcif.ts index 48e9970cecf8844b86fe7a2312c9f2f1cbd1c55b..67caefba3cf944ee898cf3463440739a5e8a96c2 100644 --- a/src/mol-model-formats/structure/mmcif.ts +++ b/src/mol-model-formats/structure/mmcif.ts @@ -18,6 +18,7 @@ import { AtomSiteAnisotrop } from './property/anisotropic'; import { ComponentBond } from './property/bonds/chem_comp'; import { StructConn } from './property/bonds/struct_conn'; import { Trajectory } from '../../mol-model/structure'; +import { GlobalModelTransformInfo } from '../../mol-model/structure/model/properties/global-transform'; function modelSymmetryFromMmcif(model: Model) { if (!MmcifFormat.is(model.sourceData)) return; @@ -69,6 +70,8 @@ function structConnFromMmcif(model: Model) { } StructConn.Provider.formatRegistry.add('mmCIF', structConnFromMmcif); +GlobalModelTransformInfo.Provider.formatRegistry.add('mmCIF', GlobalModelTransformInfo.fromMmCif, GlobalModelTransformInfo.hasData); + // export { MmcifFormat }; diff --git a/src/mol-model/structure/export/mmcif.ts b/src/mol-model/structure/export/mmcif.ts index fffe06a4c05e0e8df131e053669fc36779f0424b..2754e52d6cb42ca05fe873af0f87d1dabbfcdd12 100644 --- a/src/mol-model/structure/export/mmcif.ts +++ b/src/mol-model/structure/export/mmcif.ts @@ -132,7 +132,8 @@ function getCustomPropCategories(customProp: CustomPropertyDescriptor, ctx: CifE type encode_mmCIF_categories_Params = { skipCategoryNames?: Set<string>, exportCtx?: CifExportContext, - copyAllCategories?: boolean + copyAllCategories?: boolean, + customProperties?: CustomPropertyDescriptor[] } /** Doesn't start a data block */ @@ -144,7 +145,7 @@ export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structures: const ctx: CifExportContext = params?.exportCtx || CifExportContext.create(structures); if (params?.copyAllCategories && MmcifFormat.is(models[0].sourceData)) { - encode_mmCIF_categories_copyAll(encoder, ctx); + encode_mmCIF_categories_copyAll(encoder, ctx, params); } else { encode_mmCIF_categories_default(encoder, ctx, params); } @@ -168,6 +169,14 @@ function encode_mmCIF_categories_default(encoder: CifWriter.Encoder, ctx: CifExp } } + if (params?.customProperties) { + for (const customProp of params?.customProperties) { + for (const [cat, propCtx] of getCustomPropCategories(customProp, ctx, _params)) { + encoder.writeCategory(cat, propCtx); + } + } + } + for (const s of ctx.structures) { if (!s.hasCustomProperties) continue; for (const customProp of s.customPropertyDescriptors.all) { @@ -178,7 +187,7 @@ function encode_mmCIF_categories_default(encoder: CifWriter.Encoder, ctx: CifExp } } -function encode_mmCIF_categories_copyAll(encoder: CifWriter.Encoder, ctx: CifExportContext) { +function encode_mmCIF_categories_copyAll(encoder: CifWriter.Encoder, ctx: CifExportContext, params?: encode_mmCIF_categories_Params) { const providedCategories = new Map<string, CifExportCategoryInfo>(); for (const cat of Categories) { @@ -188,12 +197,21 @@ function encode_mmCIF_categories_copyAll(encoder: CifWriter.Encoder, ctx: CifExp const mapping = atom_site_operator_mapping(ctx); if (mapping) providedCategories.set(mapping[0].name, mapping); + const _params = params || { }; for (const customProp of ctx.firstModel.customProperties.all) { - for (const info of getCustomPropCategories(customProp, ctx)) { + for (const info of getCustomPropCategories(customProp, ctx, _params)) { providedCategories.set(info[0].name, info); } } + if (params?.customProperties) { + for (const customProp of params?.customProperties) { + for (const info of getCustomPropCategories(customProp, ctx, _params)) { + providedCategories.set(info[0].name, info); + } + } + } + for (const s of ctx.structures) { if (!s.hasCustomProperties) continue; for (const customProp of s.customPropertyDescriptors.all) { diff --git a/src/mol-model/structure/model/properties/format-specific.ts b/src/mol-model/structure/model/properties/format-specific.ts deleted file mode 100644 index a978e6aca0854c64d8432445da85b89855d267fe..0000000000000000000000000000000000000000 --- a/src/mol-model/structure/model/properties/format-specific.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -// TODO add access to things like MOL2 charge ... \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/global-transform.ts b/src/mol-model/structure/model/properties/global-transform.ts new file mode 100644 index 0000000000000000000000000000000000000000..fbfbea15213694b184f8c83c6be97075561032dc --- /dev/null +++ b/src/mol-model/structure/model/properties/global-transform.ts @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { Mat4, Tensor } from '../../../../mol-math/linear-algebra'; +import { FormatPropertyProvider } from '../../../../mol-model-formats/structure/common/property'; +import { CustomPropertyDescriptor } from '../../../custom-property'; +import { CifExportContext } from '../../structure'; +import { Model } from '../model'; +import { Column, Table } from '../../../../mol-data/db'; +import { CifWriter } from '../../../../mol-io/writer/cif'; +import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif'; +import { toTable } from '../../../../mol-io/reader/cif/schema'; + +export namespace GlobalModelTransformInfo { + const CategoryName = 'molstar_global_model_transform_info' as const; + export const Schema = { + [CategoryName]: { + matrix: Column.Schema.Matrix(4, 4, Column.Schema.float) + } + }; + export type Schema = typeof Schema + + export const Descriptor = CustomPropertyDescriptor({ + name: CategoryName, + cifExport: { + categories: [{ + name: CategoryName, + instance(ctx: CifExportContext) { + const mat = get(ctx.firstModel); + if (!mat) return CifWriter.Category.Empty; + const table = Table.ofRows(Schema.molstar_global_model_transform_info, [{ matrix: mat as unknown as Tensor.Data }]); + return CifWriter.Category.ofTable(table); + } + }], + prefix: 'molstar' + } + }); + + export const Provider = FormatPropertyProvider.create<Mat4>(Descriptor); + + export function attach(model: Model, matrix: Mat4) { + if (!model.customProperties.has(Descriptor)) { + model.customProperties.add(Descriptor); + } + Provider.set(model, matrix); + } + + export function get(model: Model): Mat4 | undefined { + return Provider.get(model); + } + + export function fromMmCif(model: Model) { + if (!MmcifFormat.is(model.sourceData)) return; + + const cat = model.sourceData.data.frame.categories[CategoryName]; + if (!cat) return; + const table = toTable(Schema[CategoryName], cat); + if (table._rowCount === 0) return; + return table.matrix.value(0) as unknown as Mat4; + } + + export function hasData(model: Model) { + if (!MmcifFormat.is(model.sourceData)) return false; + const cat = model.sourceData.data.frame.categories[CategoryName]; + return !!cat && cat.rowCount > 0; + } + + export function writeMmCif(encoder: CifWriter.Encoder, matrix: Mat4) { + encoder.writeCategory({ + name: CategoryName, + instance() { + const table = Table.ofRows(Schema.molstar_global_model_transform_info, [{ matrix: matrix as unknown as Tensor.Data }]); + return CifWriter.Category.ofTable(table); + } + }); + } +} \ No newline at end of file diff --git a/src/mol-model/structure/structure/element/loci.ts b/src/mol-model/structure/structure/element/loci.ts index 1ca9372cfc32791268c186eac2b250c3e505d747..cac08956716f1e42fed46cd2c9ab796e15d912cb 100644 --- a/src/mol-model/structure/structure/element/loci.ts +++ b/src/mol-model/structure/structure/element/loci.ts @@ -7,7 +7,7 @@ import { UniqueArray } from '../../../../mol-data/generic'; import { OrderedSet, SortedArray, Interval } from '../../../../mol-data/int'; -import { Vec3 } from '../../../../mol-math/linear-algebra'; +import { Mat4, Vec3 } from '../../../../mol-math/linear-algebra'; import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder'; import Structure from '../structure'; import Unit from '../unit'; @@ -489,7 +489,7 @@ export namespace Loci { const boundaryHelper = new BoundaryHelper('98'); const tempPosBoundary = Vec3(); - export function getBoundary(loci: Loci): Boundary { + export function getBoundary(loci: Loci, transform?: Mat4): Boundary { boundaryHelper.reset(); for (const e of loci.elements) { @@ -499,6 +499,7 @@ export namespace Loci { for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) { const eI = elements[OrderedSet.getAt(indices, i)]; pos(eI, tempPosBoundary); + if (transform) Vec3.transformMat4(tempPosBoundary, tempPosBoundary, transform); boundaryHelper.includePositionRadius(tempPosBoundary, r(eI)); } } @@ -510,6 +511,7 @@ export namespace Loci { for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) { const eI = elements[OrderedSet.getAt(indices, i)]; pos(eI, tempPosBoundary); + if (transform) Vec3.transformMat4(tempPosBoundary, tempPosBoundary, transform); boundaryHelper.radiusPositionRadius(tempPosBoundary, r(eI)); } } diff --git a/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts b/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts index b088a7c9ab95c924498579f82bdb2003f2f02cf9..eec56b8dbfcb51346d8f4f02c17bea67eac2375e 100644 --- a/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts +++ b/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts @@ -10,7 +10,7 @@ import { PluginStateObject } from '../../../../mol-plugin-state/objects'; import { Volume, Grid } from '../../../../mol-model/volume'; import { VolumeServerHeader, VolumeServerInfo } from './model'; import { Box3D } from '../../../../mol-math/geometry'; -import { Vec3 } from '../../../../mol-math/linear-algebra'; +import { Mat4, Vec3 } from '../../../../mol-math/linear-algebra'; import { Color } from '../../../../mol-util/color'; import { PluginBehavior } from '../../behavior'; import { LRUCache } from '../../../../mol-util/lru-cache'; @@ -23,6 +23,7 @@ import { StructureElement, Structure } from '../../../../mol-model/structure'; import { PluginContext } from '../../../context'; import { EmptyLoci, Loci, isEmptyLoci } from '../../../../mol-model/loci'; import { Asset } from '../../../../mol-util/assets'; +import { GlobalModelTransformInfo } from '../../../../mol-model/structure/model/properties/global-transform'; export class VolumeStreaming extends PluginStateObject.CreateBehavior<VolumeStreaming.Behavior>({ name: 'Volume Streaming' }) { } @@ -302,6 +303,7 @@ export namespace VolumeStreaming { } } + private _invTransform: Mat4 = Mat4(); private getBoxFromLoci(loci: StructureElement.Loci | EmptyLoci): Box3D { if (Loci.isEmpty(loci)) { return Box3D(); @@ -312,11 +314,16 @@ export namespace VolumeStreaming { const root = this.getStructureRoot(); if (!root || root.obj?.data !== parent.obj?.data) return Box3D(); + const transform = GlobalModelTransformInfo.get(root.obj?.data.models[0]!); + if (transform) Mat4.invert(this._invTransform, transform); + const extendedLoci = StructureElement.Loci.extendToWholeResidues(loci); - const box = StructureElement.Loci.getBoundary(extendedLoci).box; + const box = StructureElement.Loci.getBoundary(extendedLoci, transform && !Number.isNaN(this._invTransform[0]) ? this._invTransform : void 0).box; + if (StructureElement.Loci.size(extendedLoci) === 1) { Box3D.expand(box, box, Vec3.create(1, 1, 1)); } + return box; } @@ -360,7 +367,6 @@ export namespace VolumeStreaming { const switchedToSelection = params.entry.params.view.name === 'selection-box' && this.params && this.params.entry && this.params.entry.params && this.params.entry.params.view && this.params.entry.params.view.name !== 'selection-box'; this.params = params; - let box: Box3D | undefined = void 0, emptyData = false; switch (params.entry.params.view.name) { diff --git a/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts b/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts index c599c069de314ec2b789da3e6bbdd855cd904177..d39ebd4dda5eaebc6eb0b5fedc2c03fc57b66b45 100644 --- a/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts +++ b/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts @@ -22,6 +22,7 @@ import { Box3D } from '../../../../mol-math/geometry'; import { Vec3 } from '../../../../mol-math/linear-algebra'; import { PluginConfig } from '../../../config'; import { Model } from '../../../../mol-model/structure'; +import { GlobalModelTransformInfo } from '../../../../mol-model/structure/model/properties/global-transform'; function addEntry(entries: InfoEntryProps[], method: VolumeServerInfo.Kind, dataId: string, emDefaultContourLevel: number) { entries.push({ @@ -253,20 +254,22 @@ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({ channel: PD.Select<VolumeStreaming.ChannelType>('em', VolumeStreaming.ChannelTypeOptions, { isHidden: true }) } })({ - apply: ({ a, params: srcParams }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => { + apply: ({ a, params: srcParams, spine }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => { const channel = a.data.channels[srcParams.channel]; if (!channel) return StateObject.Null; const params = createVolumeProps(a.data, srcParams.channel); - const provider = VolumeRepresentationRegistry.BuiltIn.isosurface; const props = params.type.params || {}; const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.volume.themes }, provider.getParams); repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: channel.data }, params)); + const structure = spine.getAncestorOfType(SO.Molecule.Structure)?.data; + const transform = structure?.models.length === 0 ? void 0 : GlobalModelTransformInfo.get(structure?.models[0]!); await repr.createOrUpdate(props, channel.data).runInContext(ctx); + if (transform) repr.setState({ transform }); return new SO.Volume.Representation3D({ repr, source: a }, { label: `${Math.round(channel.isoValue.relativeValue * 100) / 100} σ [${srcParams.channel}]` }); }), - update: ({ a, b, oldParams, newParams }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => { + update: ({ a, b, newParams, spine }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => { // TODO : check if params/underlying data/etc have changed; maybe will need to export "data" or some other "tag" in the Representation for this to work const channel = a.data.channels[newParams.channel]; @@ -277,6 +280,13 @@ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({ const props = { ...b.data.repr.props, ...params.type.params }; b.data.repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: channel.data }, params)); await b.data.repr.createOrUpdate(props, channel.data).runInContext(ctx); + + // TODO: set the transform here as well in case the structure moves? + // doing this here now breaks the code for some reason... + // const structure = spine.getAncestorOfType(SO.Molecule.Structure)?.data; + // const transform = structure?.models.length === 0 ? void 0 : GlobalModelTransformInfo.get(structure?.models[0]!); + // if (transform) b.data.repr.setState({ transform }); + return StateTransformer.UpdateResult.Updated; }) }); diff --git a/src/servers/model/CHANGELOG.md b/src/servers/model/CHANGELOG.md index f9a66cca35b1f7526ac6c8c51510a96f30345a03..e04690ee0bd27208d59d5cd531928edd15c98d5d 100644 --- a/src/servers/model/CHANGELOG.md +++ b/src/servers/model/CHANGELOG.md @@ -1,3 +1,6 @@ +# 0.9.5 +* Support molstar_global_model_transform_info category. + # 0.9.4 * bug fix for /ligand queries on metal ions diff --git a/src/servers/model/server/query.ts b/src/servers/model/server/query.ts index b7ae216f40ca65c054324282806e9bb62ea39e4f..eb32cebccb6f0382975b3250413c9349aaf707bd 100644 --- a/src/servers/model/server/query.ts +++ b/src/servers/model/server/query.ts @@ -30,6 +30,7 @@ import { MolEncoder } from '../../../mol-io/writer/mol/encoder'; import { Mol2Encoder } from '../../../mol-io/writer/mol2/encoder'; import { ComponentAtom } from '../../../mol-model-formats/structure/property/atoms/chem_comp'; import { Mat4 } from '../../../mol-math/linear-algebra'; +import { GlobalModelTransformInfo } from '../../../mol-model/structure/model/properties/global-transform'; export interface Stats { structure: StructureWrapper, @@ -247,6 +248,7 @@ async function resolveJobEntry(entry: JobEntry, structure: StructureWrapper, enc if (!entry.copyAllCategories && entry.queryDefinition.filter) encoder.setFilter(entry.queryDefinition.filter); if (result.length > 0) encode_mmCIF_categories(encoder, result, { copyAllCategories: entry.copyAllCategories }); + if (entry.transform && !Mat4.isIdentity(entry.transform)) GlobalModelTransformInfo.writeMmCif(encoder, entry.transform); if (!entry.copyAllCategories && entry.queryDefinition.filter) encoder.setFilter(); perf.end('encode'); diff --git a/src/servers/model/version.ts b/src/servers/model/version.ts index 32862495baed5f55bced92adb3be834d814b1163..1034ed52d45834deba28440eed5d972a5abbb224 100644 --- a/src/servers/model/version.ts +++ b/src/servers/model/version.ts @@ -4,4 +4,4 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -export default '0.9.4'; \ No newline at end of file +export default '0.9.5'; \ No newline at end of file