Skip to content
Snippets Groups Projects
structure-view.ts 14.3 KiB
Newer Older
Alexander Rose's avatar
wip
Alexander Rose committed
/**
 * 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';
Alexander Rose's avatar
wip
Alexander Rose committed
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';
Alexander Rose's avatar
Alexander Rose committed
import { getAxesShape } from './assembly-symmetry';
import Viewer from 'mol-canvas3d/viewer';
import { CarbohydrateRepresentation } from 'mol-geo/representation/structure/representation/carbohydrate';
Alexander Rose's avatar
Alexander Rose committed
// 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';
Alexander Rose's avatar
Alexander Rose committed
import { StructureRepresentation } from 'mol-geo/representation/structure';
import { BehaviorSubject } from 'rxjs';
import { SpacefillRepresentation } from 'mol-geo/representation/structure/representation/spacefill';
import { DistanceRestraintRepresentation } from 'mol-geo/representation/structure/representation/distance-restraint';
Alexander Rose's avatar
Alexander Rose committed
import { MolecularSurfaceRepresentation } from 'mol-geo/representation/structure/representation/molecular-surface';
import { App } from './app';
import { Progress } from 'mol-task';

export interface StructureView {
Alexander Rose's avatar
Alexander Rose committed
    readonly viewer: Viewer

    readonly label: string
    readonly models: ReadonlyArray<Model>
    readonly structure: Structure | undefined
    readonly assemblySymmetry: AssemblySymmetry | undefined
Alexander Rose's avatar
wip
Alexander Rose committed

Alexander Rose's avatar
Alexander Rose committed
    readonly active: { [k: string]: boolean }
    readonly structureRepresentations: { [k: string]: StructureRepresentation<any> }
    readonly updated: BehaviorSubject<null>
    readonly symmetryAxes: ShapeRepresentation<ShapeProps>
Alexander Rose's avatar
Alexander Rose committed
    setSymmetryAxes(value: boolean): void
    setStructureRepresentation(name: string, value: boolean): void

    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
Alexander Rose's avatar
wip
Alexander Rose committed
}

interface StructureViewProps {
    assemblyId?: string
    symmetryFeatureId?: number
export async function StructureView(app: App, viewer: Viewer, models: ReadonlyArray<Model>, props: StructureViewProps = {}): Promise<StructureView> {
Alexander Rose's avatar
Alexander Rose committed
    const active: { [k: string]: boolean } = {
        cartoon: true,
        point: false,
Alexander Rose's avatar
Alexander Rose committed
        ballAndStick: false,
Alexander Rose's avatar
Alexander Rose committed
        carbohydrate: true,
        spacefill: false,
Alexander Rose's avatar
Alexander Rose committed
        symmetryAxes: false,
Alexander Rose's avatar
Alexander Rose committed
    }

    const structureRepresentations: { [k: string]: StructureRepresentation<any> } = {
        cartoon: CartoonRepresentation(),
Alexander Rose's avatar
Alexander Rose committed
        surface: MolecularSurfaceRepresentation(),
Alexander Rose's avatar
Alexander Rose committed
        point: PointRepresentation(),
        ballAndStick: BallAndStickRepresentation(),
        carbohydrate: CarbohydrateRepresentation(),
        spacefill: SpacefillRepresentation(),
        distanceRestraint: DistanceRestraintRepresentation(),
Alexander Rose's avatar
Alexander Rose committed
    }
    const symmetryAxes = ShapeRepresentation()
    const polymerSphere = ShapeRepresentation()
Alexander Rose's avatar
Alexander Rose committed
    const updated: BehaviorSubject<null> = new BehaviorSubject<null>(null)
    let label: string
    let model: Model | undefined
    let assemblySymmetry: AssemblySymmetry | undefined
    let structure: Structure | undefined

    let modelId: number
    let assemblyId: string
    let symmetryFeatureId: number
Alexander Rose's avatar
wip
Alexander Rose committed

Alexander Rose's avatar
Alexander Rose committed
    async function setSymmetryAxes(value: boolean) {
        if (!value) {
            assemblySymmetry = undefined
        } else {
            await app.runTask(AssemblySymmetry.attachFromCifOrAPI(models[modelId]), 'Load symmetry annotation')
Alexander Rose's avatar
Alexander Rose committed
            assemblySymmetry = AssemblySymmetry.get(models[modelId])
        }
        active.symmetryAxes = value
        await setSymmetryFeature()
    }

    async function setStructureRepresentation(k: string, value: boolean) {
        active[k] = value
        await createStructureRepr()
    }

    async function setModel(newModelId: number, newAssemblyId?: string, newSymmetryFeatureId?: number) {
        console.log('setModel', newModelId)
        modelId = newModelId
        model = models[modelId]
Alexander Rose's avatar
Alexander Rose committed
        if (active.symmetryAxes) {
            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)
Alexander Rose's avatar
wip
Alexander Rose committed
    }

    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
Alexander Rose's avatar
wip
Alexander Rose committed
    }

    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 app.runTask(getStructureFromModel(model, assemblyId), 'Build structure')
        if (model && structure) {
            label = `${model.label} - Assembly ${assemblyId}`
        } else {
            label = ''
        }
        await createStructureRepr()
    }

    async function createStructureRepr() {
        if (structure) {
            console.log('createStructureRepr')
Alexander Rose's avatar
Alexander Rose committed
            for (const k in structureRepresentations) {
                if (active[k]) {
Alexander Rose's avatar
Alexander Rose committed
                    const p = { webgl: viewer.webgl }
                    await app.runTask(structureRepresentations[k].createOrUpdate(p, structure).run(
                        progress => console.log(Progress.format(progress))
                    ), 'Create/update representation')
Alexander Rose's avatar
Alexander Rose committed
                    viewer.add(structureRepresentations[k])
                } else {
                    viewer.remove(structureRepresentations[k])
                }
Alexander Rose's avatar
Alexander Rose committed
            }
            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()
Alexander Rose's avatar
Alexander Rose committed
            for (const k in structureRepresentations) structureRepresentations[k].destroy()
Alexander Rose's avatar
Alexander Rose committed

        updated.next(null)
        viewer.requestDraw(true)
Alexander Rose's avatar
Alexander Rose committed
        console.log('stats', viewer.stats)
    }

    async function createSymmetryRepr() {
        if (assemblySymmetry) {
            const features = assemblySymmetry.getFeatures(assemblyId)
            if (features._rowCount) {
                const axesShape = getAxesShape(symmetryFeatureId, assemblySymmetry)
                if (axesShape) {
Alexander Rose's avatar
Alexander Rose committed
                    // const colorTheme = getClusterColorTheme(symmetryFeatureId, assemblySymmetry)
                    // await cartoon.createOrUpdate({
Alexander Rose's avatar
Alexander Rose committed
                    //     colorTheme: { name: 'custom', color: colorTheme.color, granularity: colorTheme.granularity },
                    //     sizeTheme: { name: 'uniform', value: 0.2 },
                    //     useFog: false // TODO fog not working properly
                    // }).run()
                    // await ballAndStick.createOrUpdate({
Alexander Rose's avatar
Alexander Rose committed
                    //     colorTheme:  { name: 'custom', color: colorTheme.color, granularity: colorTheme.granularity },
                    //     sizeTheme: { name: 'uniform', value: 0.1 },
                    //     useFog: false // TODO fog not working properly
                    // }).run()
Alexander Rose's avatar
Alexander Rose committed
                    await symmetryAxes.createOrUpdate({}, axesShape).run()
Alexander Rose's avatar
Alexander Rose committed
                    viewer.add(symmetryAxes)
Alexander Rose's avatar
Alexander Rose committed
                    viewer.remove(symmetryAxes)
Alexander Rose's avatar
Alexander Rose committed
                viewer.remove(symmetryAxes)
Alexander Rose's avatar
Alexander Rose committed
            viewer.remove(symmetryAxes)
Alexander Rose's avatar
Alexander Rose committed
        updated.next(null)
        viewer.requestDraw(true)
    await setModel(0, props.assemblyId, props.symmetryFeatureId)
Alexander Rose's avatar
wip
Alexander Rose committed

    return {
Alexander Rose's avatar
Alexander Rose committed
        viewer,

        get label() { return label },
        models,
Alexander Rose's avatar
wip
Alexander Rose committed
        get structure() { return structure },
        get assemblySymmetry() { return assemblySymmetry },

Alexander Rose's avatar
Alexander Rose committed
        active,
Alexander Rose's avatar
Alexander Rose committed
        structureRepresentations,
Alexander Rose's avatar
Alexander Rose committed
        updated,
Alexander Rose's avatar
Alexander Rose committed
        setSymmetryAxes,
        setStructureRepresentation,

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

        setAssembly,
        getAssemblyIds,
        setSymmetryFeature,
        getSymmetryFeatureIds,

        destroy: () => {
Alexander Rose's avatar
Alexander Rose committed
            for (const k in structureRepresentations) {
                viewer.remove(structureRepresentations[k])
                structureRepresentations[k].destroy()
            }
Alexander Rose's avatar
Alexander Rose committed
            viewer.remove(polymerSphere)
            viewer.requestDraw(true)
Alexander Rose's avatar
wip
Alexander Rose committed

Alexander Rose's avatar
Alexander Rose committed
            polymerSphere.destroy()
Alexander Rose's avatar
wip
Alexander Rose committed
    }
}

// // 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)