Skip to content
Snippets Groups Projects
structure-view.ts 12.82 KiB
/**
 * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
 *
 * @author Alexander Rose <alexander.rose@weirdbyte.de>
 */

import { Model, Structure } from 'mol-model/structure';
import { CartoonRepresentation } from 'mol-geo/representation/structure/representation/cartoon';
import { BallAndStickRepresentation } from 'mol-geo/representation/structure/representation/ball-and-stick';
import { getStructureFromModel } from './util';
import { AssemblySymmetry } from 'mol-model-props/rcsb/symmetry';
import { ShapeRepresentation, ShapeProps } from 'mol-geo/representation/shape';
import { getAxesShape, getClusterColorTheme } from './assembly-symmetry';
import Viewer from 'mol-view/viewer';
import { CarbohydrateRepresentation } from 'mol-geo/representation/structure/representation/carbohydrate';
import { MeshBuilder } from 'mol-geo/mesh/mesh-builder';
import { addSphere } from 'mol-geo/mesh/builder/sphere';
import { Shape } from 'mol-model/shape';
import { Color } from 'mol-util/color';
import { computeUnitBoundary } from 'mol-model/structure/structure/util/boundary';
import { addBoundingBox } from 'mol-geo/mesh/builder/bounding-box';
import { PointRepresentation } from 'mol-geo/representation/structure/representation/point';

export interface StructureView {
    readonly label: string
    readonly models: ReadonlyArray<Model>
    readonly structure: Structure | undefined
    readonly assemblySymmetry: AssemblySymmetry | undefined

    readonly cartoon: CartoonRepresentation
    readonly ballAndStick: BallAndStickRepresentation
    readonly carbohydrate: CarbohydrateRepresentation
    readonly symmetryAxes: ShapeRepresentation<ShapeProps>

    readonly modelId: number
    readonly assemblyId: string
    readonly symmetryFeatureId: number

    setModel(modelId: number): Promise<void>
    getModelIds(): { id: number, label: string }[]
    setAssembly(assemblyId: string): Promise<void>
    getAssemblyIds(): { id: string, label: string }[]
    setSymmetryFeature(symmetryFeatureId: number): Promise<void>
    getSymmetryFeatureIds(): { id: number, label: string }[]

    destroy: () => void
}

interface StructureViewProps {
    assemblyId?: string
    symmetryFeatureId?: number
}

export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model>, props: StructureViewProps = {}): Promise<StructureView> {
    const cartoon = CartoonRepresentation()
    const point = PointRepresentation()
    const ballAndStick = BallAndStickRepresentation()
    const carbohydrate = CarbohydrateRepresentation()
    const symmetryAxes = ShapeRepresentation()
    const polymerSphere = ShapeRepresentation()

    let label: string
    let model: Model | undefined
    let assemblySymmetry: AssemblySymmetry | undefined
    let structure: Structure | undefined

    let modelId: number
    let assemblyId: string
    let symmetryFeatureId: number
    async function setModel(newModelId: number, newAssemblyId?: string, newSymmetryFeatureId?: number) {
        console.log('setModel', newModelId)
        modelId = newModelId
        model = models[modelId]
        await AssemblySymmetry.attachFromCifOrAPI(model)
        assemblySymmetry = AssemblySymmetry.get(model)
        await setAssembly(newAssemblyId, newSymmetryFeatureId)
    }

    function getModelIds() {
        const modelIds: { id: number, label: string }[] = []
        models.forEach((m, i) => {
            modelIds.push({ id: i, label: `${i}: ${m.label} #${m.modelNum}` })
        })
        return modelIds
    }

    async function setAssembly(newAssemblyId?: string, newSymmetryFeatureId?: number) {
        console.log('setAssembly', newAssemblyId)
        if (newAssemblyId !== undefined) {
            assemblyId = newAssemblyId
        } else if (model && model.symmetry.assemblies.length) {
            assemblyId = model.symmetry.assemblies[0].id
        } else if (model) {
            assemblyId = '0'
        } else {
            assemblyId = '-1'
        }
        await getStructure()
        await setSymmetryFeature(newSymmetryFeatureId)
    }

    function getAssemblyIds() {
        const assemblyIds: { id: string, label: string }[] = [
            { id: '0', label: '0: model' }
        ]
        if (model) model.symmetry.assemblies.forEach(a => {
            assemblyIds.push({ id: a.id, label: `${a.id}: ${a.details}` })
        })
        return assemblyIds
    }

    async function setSymmetryFeature(newSymmetryFeatureId?: number) {
        console.log('setSymmetryFeature', newSymmetryFeatureId)
        if (newSymmetryFeatureId !== undefined) {
            symmetryFeatureId = newSymmetryFeatureId
        } else if (assemblySymmetry) {
            const f = assemblySymmetry.getFeatures(assemblyId)
            if (f._rowCount) {
                symmetryFeatureId = f.id.value(0)
            } else {
                symmetryFeatureId = -1
            }
        } else {
            symmetryFeatureId = -1
        }
        await createSymmetryRepr()
    }

    function getSymmetryFeatureIds() {
        const symmetryFeatureIds: { id: number, label: string }[] = []
        if (assemblySymmetry) {
            const symmetryFeatures = assemblySymmetry.getFeatures(assemblyId)
            for (let i = 0, il = symmetryFeatures._rowCount; i < il; ++i) {
                const id = symmetryFeatures.id.value(i)
                const symmetry = symmetryFeatures.symmetry_value.value(i)
                const type = symmetryFeatures.type.value(i)
                const stoichiometry = symmetryFeatures.stoichiometry_value.value(i)
                const label = `${id}: ${symmetry} ${type} ${stoichiometry}`
                symmetryFeatureIds.push({ id, label })
            }
        }
        return symmetryFeatureIds
    }

    async function getStructure() {
        if (model) structure = await getStructureFromModel(model, assemblyId)
        if (model && structure) {
            label = `${model.label} - Assembly ${assemblyId}`
        } else {
            label = ''
        }
        await createStructureRepr()
    }

    async function createStructureRepr() {
        if (structure) {
            console.log('createStructureRepr')
            await cartoon.createOrUpdate({
                colorTheme: { name: 'chain-id' },
                sizeTheme: { name: 'uniform', value: 0.2 },
                useFog: false // TODO fog not working properly
            }, structure).run()

            // await point.createOrUpdate({
            //     colorTheme: { name: 'unit-index' },
            //     sizeTheme: { name: 'uniform', value: 0.2 },
            //     useFog: false // TODO fog not working properly
            // }, structure).run()

            await ballAndStick.createOrUpdate({
                colorTheme: { name: 'chain-id' },
                sizeTheme: { name: 'uniform', value: 0.1 },
                useFog: false // TODO fog not working properly
            }, structure).run()

            // await carbohydrate.createOrUpdate({
            //     colorTheme: { name: 'carbohydrate-symbol' },
            //     sizeTheme: { name: 'uniform', value: 1, factor: 1 },
            //     useFog: false // TODO fog not working properly
            // }, structure).run()

            viewer.center(structure.boundary.sphere.center)

            // const mb = MeshBuilder.create()
            // mb.setGroup(0)
            // addSphere(mb, structure.boundary.sphere.center, structure.boundary.sphere.radius, 3)
            // addBoundingBox(mb, structure.boundary.box, 1, 2, 8)
            // for (let i = 0, il = structure.units.length; i < il; ++i) {
            //     mb.setGroup(1)
            //     const u = structure.units[i]
            //     const ci = u.model.atomicHierarchy.chainAtomSegments.index[u.elements[0]]
            //     const ek = u.model.atomicHierarchy.getEntityKey(ci)
            //     if (u.model.entities.data.type.value(ek) === 'water') continue
            //     const boundary = computeUnitBoundary(u)
            //     addSphere(mb, boundary.sphere.center, boundary.sphere.radius, 3)
            //     addBoundingBox(mb, boundary.box, 0.5, 2, 8)
            // }
            // const shape = Shape.create('boundary', mb.getMesh(), [Color(0xCC6633), Color(0x3366CC)], ['sphere boundary'])
            // await polymerSphere.createOrUpdate({
            //     alpha: 0.5,
            //     doubleSided: false,
            //     depthMask: false,
            //     useFog: false // TODO fog not working properly
            // }, shape).run()
        } else {
            cartoon.destroy()
            point.destroy()
            ballAndStick.destroy()
            carbohydrate.destroy()
            polymerSphere.destroy()
        }

        viewer.add(cartoon)
        viewer.add(point)
        viewer.add(ballAndStick)
        viewer.add(carbohydrate)
        viewer.add(polymerSphere)
    }

    async function createSymmetryRepr() {
        if (assemblySymmetry) {
            const features = assemblySymmetry.getFeatures(assemblyId)
            if (features._rowCount) {
                const axesShape = getAxesShape(symmetryFeatureId, assemblySymmetry)
                if (axesShape) {
                    const colorTheme = getClusterColorTheme(symmetryFeatureId, assemblySymmetry)
                    // await cartoon.createOrUpdate({
                    //     colorTheme: { name: 'custom', ...colorTheme },
                    //     sizeTheme: { name: 'uniform', value: 0.2 },
                    //     useFog: false // TODO fog not working properly
                    // }).run()
                    // await ballAndStick.createOrUpdate({
                    //     colorTheme:  { name: 'custom', ...colorTheme },
                    //     sizeTheme: { name: 'uniform', value: 0.1 },
                    //     useFog: false // TODO fog not working properly
                    // }).run()
                    await symmetryAxes.createOrUpdate({
                        colorTheme: { name: 'shape-group', ...colorTheme },
                        // colorTheme: { name: 'uniform', value: Color(0xFFCC22) },
                        useFog: false // TODO fog not working properly
                    }, axesShape).run()
                } else {
                    symmetryAxes.destroy()
                }
            } else {
                symmetryAxes.destroy()
            }
        } else {
            symmetryAxes.destroy()
        }
        viewer.add(symmetryAxes)
        viewer.requestDraw()
    }

    await setModel(0, props.assemblyId, props.symmetryFeatureId)

    return {
        get label() { return label },
        models,
        get structure() { return structure },
        get assemblySymmetry() { return assemblySymmetry },

        cartoon,
        ballAndStick,
        carbohydrate,
        symmetryAxes,

        get modelId() { return modelId },
        get assemblyId() { return assemblyId },
        get symmetryFeatureId() { return symmetryFeatureId },

        setModel,
        getModelIds,
        setAssembly,
        getAssemblyIds,
        setSymmetryFeature,
        getSymmetryFeatureIds,

        destroy: () => {
            viewer.remove(cartoon)
            viewer.remove(ballAndStick)
            viewer.remove(carbohydrate)
            viewer.remove(symmetryAxes)
            viewer.requestDraw()

            cartoon.destroy()
            ballAndStick.destroy()
            symmetryAxes.destroy()
        }
    }
}

// // create new structure via query
// const q1 = Q.generators.atoms({
//     residueTest: qtx => SP.residue.label_seq_id(qtx.element) < 7
// });
// const newStructure = StructureSelection.unionStructure(await StructureQuery.run(q1, structure));

// // ball+stick for new structure
// const newBallStickRepr = BallAndStickRepresentation()
// await newBallStickRepr.create(newStructure, {
//     colorTheme: { name: 'element-symbol' },
//     sizeTheme: { name: 'uniform', value: 0.1 },
//     useFog: false // TODO fog not working properly
// }).run()
// viewer.add(newBallStickRepr)

// // create a mesh
// const meshBuilder = MeshBuilder.create(256, 128)
// const colors: Color[] = []
// const labels: string[] = []
// // red sphere
// meshBuilder.setGroup(0)
// colors[0] = Color(0xFF2233)
// labels[0] = 'red sphere'
// addSphere(meshBuilder, Vec3.create(0, 0, 0), 4, 2)
// // green cube
// meshBuilder.setGroup(1)
// colors[1] = Color(0x2233FF)
// labels[1] = 'blue cube'
// const t = Mat4.identity()
// Mat4.fromTranslation(t, Vec3.create(10, 0, 0))
// Mat4.scale(t, t, Vec3.create(3, 3, 3))
// meshBuilder.add(t, Box())
// const mesh = meshBuilder.getMesh()
// const mesh = getObjFromUrl('mesh.obj')

// // create shape from mesh
// const shape = Shape.create('myShape', mesh, colors, labels)

// // add representation from shape
// const customRepr = ShapeRepresentation()
// await customRepr.create(shape, {
//     colorTheme: { name: 'shape-group' },
//     // colorTheme: { name: 'uniform', value: Color(0xFFCC22) },
//     useFog: false // TODO fog not working properly
// }).run()
// viewer.add(customRepr)