Skip to content
Snippets Groups Projects
basic.ts 8.47 KiB
/**
 * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
 *
 * @author David Sehnal <david.sehnal@gmail.com>
 */

import { StateAction } from 'mol-state/action';
import { PluginStateObject } from '../objects';
import { StateTransforms } from '../transforms';
import { ParamDefinition as PD } from 'mol-util/param-definition';
import { StateSelection } from 'mol-state/state/selection';
import { CartoonParams } from 'mol-repr/structure/representation/cartoon';
import { BallAndStickParams } from 'mol-repr/structure/representation/ball-and-stick';
import { Download } from '../transforms/data';
import { StateTree } from 'mol-state';
import { StateTreeBuilder } from 'mol-state/tree/builder';
import { PolymerIdColorThemeParams } from 'mol-theme/color/polymer-id';
import { UniformSizeThemeParams } from 'mol-theme/size/uniform';
import { ElementSymbolColorThemeParams } from 'mol-theme/color/element-symbol';

// TODO: "structure parser provider"

export { DownloadStructure }
namespace DownloadStructure {
    export type Source = PD.NamedParamUnion<ObtainStructureHelpers.ControlMap>
    export interface Params {
        source: Source
    }
}
namespace ObtainStructureHelpers {
    export const ControlMap = {
        'pdbe-updated': PD.Text('1cbs', { label: 'Id' }),
        'rcsb': PD.Text('1tqn', { label: 'Id' }),
        'bcif-static': PD.Text('1tqn', { label: 'Id' }),
        'url': PD.Group({ url: PD.Text(''), isBinary: PD.Boolean(false) }, { isExpanded: true })
    }
    export type ControlMap = typeof ControlMap
    export const SourceOptions: [keyof ControlMap, string][] = [
        ['pdbe-updated', 'PDBe Updated'],
        ['rcsb', 'RCSB'],
        ['bcif-static', 'BinaryCIF (static PDBe Updated)'],
        ['url', 'URL']
    ];

    export function getControls(key: string) { return (ControlMap as any)[key]; }
    export function getUrl(src: DownloadStructure.Source): Download.Params {
        switch (src.name) {
            case 'url': return src.params;
            case 'pdbe-updated': return { url: `https://www.ebi.ac.uk/pdbe/static/entry/${src.params.toLowerCase()}_updated.cif`, isBinary: false, label: `PDBe: ${src.params}` };
            case 'rcsb': return { url: `https://files.rcsb.org/download/${src.params.toUpperCase()}.cif`, isBinary: false, label: `RCSB: ${src.params}` };
            case 'bcif-static': return { url: `https://webchem.ncbr.muni.cz/ModelServer/static/bcif/${src.params.toLowerCase()}`, isBinary: true, label: `BinaryCIF: ${src.params}` };
            default: throw new Error(`${(src as any).name} not supported.`);
        }
    }
}
const DownloadStructure = StateAction.create<PluginStateObject.Root, void, DownloadStructure.Params>({
    from: [PluginStateObject.Root],
    display: {
        name: 'Download Structure',
        description: 'Load a structure from PDBe and create its default Assembly and visual'
    },
    params: () => ({ source: PD.Mapped('bcif-static', ObtainStructureHelpers.SourceOptions, ObtainStructureHelpers.getControls) }),
    apply({ params, state }) {
        const b = state.build();

        // const query = MolScriptBuilder.struct.generator.atomGroups({
        //     // 'atom-test': MolScriptBuilder.core.rel.eq([
        //     //     MolScriptBuilder.struct.atomProperty.macromolecular.label_comp_id(),
        //     //     MolScriptBuilder.es('C')
        //     // ]),
        //     'residue-test': MolScriptBuilder.core.rel.eq([
        //         MolScriptBuilder.struct.atomProperty.macromolecular.label_comp_id(),
        //         'ALA'
        //     ])
        // });

        const url = ObtainStructureHelpers.getUrl(params.source);

        const data = b.toRoot().apply(StateTransforms.Data.Download, url);
        return state.update(createStructureTree(data));
    }
});

export const OpenStructure = StateAction.create<PluginStateObject.Root, void, { file: File }>({
    from: [PluginStateObject.Root],
    display: {
        name: 'Open Structure',
        description: 'Load a structure from file and create its default Assembly and visual'
    },
    params: () => ({ file: PD.File({ accept: '.cif,.bcif' }) }),
    apply({ params, state }) {
        const b = state.build();
        const data = b.toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: /\.bcif$/i.test(params.file.name) });
        return state.update(createStructureTree(data));
    }
});

function createStructureTree(b: StateTreeBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>): StateTree {
    const root = b
        .apply(StateTransforms.Data.ParseCif)
        .apply(StateTransforms.Model.TrajectoryFromMmCif, {})
        .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 })
        .apply(StateTransforms.Model.StructureAssemblyFromModel);

    complexRepresentation(root);

    return root.getTree();
}

function complexRepresentation(root: StateTreeBuilder.To<PluginStateObject.Molecule.Structure>) {
    root.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' })
        .apply(StateTransforms.Representation.StructureRepresentation3D, {
            type: { name: 'cartoon', params: PD.getDefaultValues(CartoonParams) },
            colorTheme: { name: 'polymer-id', params: PD.getDefaultValues(PolymerIdColorThemeParams) },
            sizeTheme: { name: 'uniform', params: PD.getDefaultValues(UniformSizeThemeParams) },
        });
    root.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' })
        .apply(StateTransforms.Representation.StructureRepresentation3D, {
            type: { name: 'ball-and-stick', params: PD.getDefaultValues(BallAndStickParams) },
            colorTheme: { name: 'element-symbol', params: PD.getDefaultValues(ElementSymbolColorThemeParams) },
            sizeTheme: { name: 'uniform', params: PD.getDefaultValues(UniformSizeThemeParams) },
        });
    root.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' })
        .apply(StateTransforms.Representation.StructureRepresentation3D, {
            type: { name: 'ball-and-stick', params: { ...PD.getDefaultValues(BallAndStickParams), alpha: 0.51 } },
            colorTheme: { name: 'element-symbol', params: PD.getDefaultValues(ElementSymbolColorThemeParams) },
            sizeTheme: { name: 'uniform', params: PD.getDefaultValues(UniformSizeThemeParams) },
        })
    root.apply(StateTransforms.Model.StructureComplexElement, { type: 'spheres' });
        // TODO: create spheres visual
}

export const CreateComplexRepresentation = StateAction.create<PluginStateObject.Molecule.Structure, void, {}>({
    from: [PluginStateObject.Molecule.Structure],
    display: {
        name: 'Create Complex',
        description: 'Split the structure into Sequence/Water/Ligands/... '
    },
    apply({ ref, state }) {
        const root = state.build().to(ref);
        complexRepresentation(root);
        return state.update(root.getTree());
    }
});

export const UpdateTrajectory = StateAction.create<PluginStateObject.Root, void, { action: 'advance' | 'reset', by?: number }>({
    from: [],
    display: {
        name: 'Update Trajectory'
    },
    params: () => ({
        action: PD.Select('advance', [['advance', 'Advance'], ['reset', 'Reset']]),
        by: PD.Numeric(1, { min: -1, max: 1, step: 1 }, { isOptional: true })
    }),
    apply({ params, state }) {
        const models = state.select(q => q.rootsOfType(PluginStateObject.Molecule.Model)
            .filter(c => c.transform.transformer === StateTransforms.Model.ModelFromTrajectory));

        const update = state.build();

        if (params.action === 'reset') {
            for (const m of models) {
                update.to(m.transform.ref).update(StateTransforms.Model.ModelFromTrajectory,
                    () => ({ modelIndex: 0 }));
            }
        } else {
            for (const m of models) {
                const parent = StateSelection.findAncestorOfType(state.tree, state.cells, m.transform.ref, [PluginStateObject.Molecule.Trajectory]);
                if (!parent || !parent.obj) continue;
                const traj = parent.obj as PluginStateObject.Molecule.Trajectory;
                update.to(m.transform.ref).update(StateTransforms.Model.ModelFromTrajectory,
                    old => {
                        let modelIndex = (old.modelIndex + params.by!) % traj.data.length;
                        if (modelIndex < 0) modelIndex += traj.data.length;
                        return { modelIndex };
                    });
            }
        }

        return state.update(update);
    }
});