diff --git a/src/mol-geo/geometry/overpaint-data.ts b/src/mol-geo/geometry/overpaint-data.ts index 54dee822b3eaa9f99eeba242441f20a06a1a3de1..e10c73d3cdb821738c15b8f630e45aeaf8a3598f 100644 --- a/src/mol-geo/geometry/overpaint-data.ts +++ b/src/mol-geo/geometry/overpaint-data.ts @@ -15,10 +15,10 @@ export type OverpaintData = { dOverpaint: ValueCell<boolean>, } -export function applyOverpaintColor(array: Uint8Array, start: number, end: number, color: Color) { +export function applyOverpaintColor(array: Uint8Array, start: number, end: number, color: Color, alpha: number) { for (let i = start; i < end; ++i) { Color.toArray(color, array, i * 4) - array[i * 4 + 3] = 255 + array[i * 4 + 3] = alpha * 255 } return true } diff --git a/src/mol-plugin/behavior/dynamic/animation.ts b/src/mol-plugin/behavior/dynamic/animation.ts index 73dab53e6a8b6fecc0b609f0af81aa3f183bc6b4..b6d6e21d11b51d12d46b8f2691c83cde513aebfb 100644 --- a/src/mol-plugin/behavior/dynamic/animation.ts +++ b/src/mol-plugin/behavior/dynamic/animation.ts @@ -25,7 +25,6 @@ type StructureAnimationProps = PD.Values<typeof StructureAnimationParams> /** * TODO * - animation class is just for testing purposes, needs better API - * - allow per-unit transform `unitTransform: { [unitId: number]: Mat4 }` */ export const StructureAnimation = PluginBehavior.create<StructureAnimationProps>({ name: 'structure-animation', diff --git a/src/mol-plugin/behavior/dynamic/representation.ts b/src/mol-plugin/behavior/dynamic/representation.ts index 47dab0e53a7d96c43a7fb664fe39d3c5141fb479..451f9a0b08340a8ca434fa12eedee49af01803cd 100644 --- a/src/mol-plugin/behavior/dynamic/representation.ts +++ b/src/mol-plugin/behavior/dynamic/representation.ts @@ -1,12 +1,13 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2019 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 { MarkerAction } from 'mol-geo/geometry/marker-data'; import { Mat4, Vec3 } from 'mol-math/linear-algebra'; -import { EmptyLoci } from 'mol-model/loci'; +import { EmptyLoci, EveryLoci } from 'mol-model/loci'; import { StructureUnitTransforms } from 'mol-model/structure/structure/util/unit-transforms'; import { PluginContext } from 'mol-plugin/context'; import { PluginStateObject } from 'mol-plugin/state/objects'; @@ -16,7 +17,15 @@ import { ParamDefinition as PD } from 'mol-util/param-definition'; import { PluginBehavior } from '../behavior'; import { Representation } from 'mol-repr/representation'; import { ButtonsType } from 'mol-util/input/input-observer'; -import { StructureElement } from 'mol-model/structure'; +import { StructureElement, StructureSelection, QueryContext } from 'mol-model/structure'; +import { ColorNames } from 'mol-util/color/tables'; +// import { MolScriptBuilder as MS } from 'mol-script/language/builder'; +import Expression from 'mol-script/language/expression'; +import { Color } from 'mol-util/color'; +import { compile } from 'mol-script/runtime/query/compiler'; +import { Overpaint } from 'mol-theme/overpaint'; +import { parseMolScript } from 'mol-script/language/parser'; +import { transpileMolScript } from 'mol-script/script/mol-script/symbols'; export const HighlightLoci = PluginBehavior.create({ name: 'representation-highlight-loci', @@ -119,7 +128,6 @@ export const DefaultLociLabelProvider = PluginBehavior.create({ display: { name: 'Provide Default Loci Label' } }); - export namespace ExplodeRepresentation3D { export const Params = { t: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }) @@ -196,4 +204,87 @@ export namespace ExplodeRepresentation3D { } export class Obj extends PluginStateObject.CreateBehavior<Behavior>({ name: 'Explode Representation3D Behavior' }) { } +} + +type ColorMappings = { query: Expression, color: Color }[] +namespace ColorMappings { + export function areEqual(colorMappingsA: ColorMappings, colorMappingsB: ColorMappings) { + return false + } +} + +export namespace ColorRepresentation3D { + export const Params = { + query: PD.ScriptExpression({ language: 'mol-script', expression: '(sel.atom.atom-groups :residue-test (= atom.resname LYS))' }), + color: PD.Color(ColorNames.blueviolet) + // colorMappings: PD.Value<ColorMappings>([{ query: MS.struct.generator.atomGroups({ + // 'residue-test': MS.core.rel.eq([MS.ammp('auth_comp_id'), 'ALA']) + // }), color: ColorNames.greenyellow }], { isHidden: true }), + } + export type Params = PD.Values<typeof Params> + + export class Behavior implements PluginBehavior<Params> { + private currentColorMappings: ColorMappings = []; + private repr: StateObjectTracker<PluginStateObject.Molecule.Representation3D>; + private structure: StateObjectTracker<PluginStateObject.Molecule.Structure>; + + private updateData() { + const reprUpdated = this.repr.update(); + const strucUpdated = this.structure.update(); + return reprUpdated || strucUpdated; + } + + register(ref: string): void { + this.repr.setQuery(StateSelection.Generators.byRef(ref).ancestorOfType([PluginStateObject.Molecule.Representation3D])); + this.structure.setQuery(StateSelection.Generators.byRef(ref).ancestorOfType([PluginStateObject.Molecule.Structure])); + this.update(this.params); + } + + update(params: Params): boolean { + const parsed = parseMolScript(params.query.expression); + if (parsed.length === 0) throw new Error('No query'); + const query = transpileMolScript(parsed[0]); + + return this.apply([{ query, color: params.color }]) + } + + private apply(colorMappings: ColorMappings): boolean { + if (!this.updateData() && ColorMappings.areEqual(colorMappings, this.currentColorMappings)) return false; + this.currentColorMappings = colorMappings; + if (!this.repr.data || !this.structure.data) return true; + + const layers: Overpaint.Layers = [ + // unsets the overpaint + // TODO do smarter by looking at the current color mappings + { loci: EveryLoci, color: ColorNames.black, alpha: 0 } + ] + // console.log('currentColorMappings', this.currentColorMappings) + for (let i = 0, il = this.currentColorMappings.length; i < il; ++i) { + const { query, color } = this.currentColorMappings[i] + const compiled = compile<StructureSelection>(query); + const result = compiled(new QueryContext(this.structure.data)); + const loci = StructureSelection.toLoci2(result) + layers.push({ loci, color, alpha: 1 }) + } + this.repr.data.setOverpaint(layers) + + this.ctx.canvas3d.add(this.repr.data); + this.ctx.canvas3d.requestDraw(true); + + return true; + } + + unregister(): void { + this.apply([]) + this.repr.cell = void 0; + this.structure.cell = void 0; + } + + constructor(private ctx: PluginContext, private params: Params) { + this.repr = new StateObjectTracker(ctx.state.dataState); + this.structure = new StateObjectTracker(ctx.state.dataState); + } + } + + export class Obj extends PluginStateObject.CreateBehavior<Behavior>({ name: 'Color Representation3D Behavior' }) { } } \ No newline at end of file diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts index 95d5b9673a3fc14fd36ab38b69714d0f9d6e708a..2b3dad790cc67a09f8121b513cf737a8863829a9 100644 --- a/src/mol-plugin/index.ts +++ b/src/mol-plugin/index.ts @@ -45,6 +45,7 @@ export const DefaultPluginSpec: PluginSpec = { PluginSpec.Action(StateTransforms.Representation.StructureRepresentation3D), PluginSpec.Action(StateTransforms.Representation.StructureLabels3D), PluginSpec.Action(StateTransforms.Representation.ExplodeStructureRepresentation3D), + PluginSpec.Action(StateTransforms.Representation.ColorStructureRepresentation3D), PluginSpec.Action(StateTransforms.Representation.VolumeRepresentation3D), PluginSpec.Action(StateActions.Structure.StructureFromSelection), diff --git a/src/mol-plugin/state/transforms/representation.ts b/src/mol-plugin/state/transforms/representation.ts index 6b8d31f51c850c5d7ead6ee5cdabfbf2b523c699..1617502a8f9c81a6f5744df1cdca3fc94060c1c2 100644 --- a/src/mol-plugin/state/transforms/representation.ts +++ b/src/mol-plugin/state/transforms/representation.ts @@ -7,7 +7,7 @@ import { Structure } from 'mol-model/structure'; import { VolumeData, VolumeIsoValue } from 'mol-model/volume'; -import { ExplodeRepresentation3D } from 'mol-plugin/behavior/dynamic/representation'; +import { ExplodeRepresentation3D, ColorRepresentation3D } from 'mol-plugin/behavior/dynamic/representation'; import { PluginContext } from 'mol-plugin/context'; import { RepresentationProvider } from 'mol-repr/representation'; import { BuiltInStructureRepresentationsName } from 'mol-repr/structure/registry'; @@ -30,6 +30,7 @@ export { StructureRepresentation3D } export { StructureRepresentation3DHelpers } export { StructureLabels3D} export { ExplodeStructureRepresentation3D } +export { ColorStructureRepresentation3D } export { VolumeRepresentation3D } namespace StructureRepresentation3DHelpers { @@ -228,7 +229,6 @@ const StructureLabels3D = PluginStateTransform.BuiltIn({ } }); - type ExplodeStructureRepresentation3D = typeof ExplodeStructureRepresentation3D const ExplodeStructureRepresentation3D = PluginStateTransform.BuiltIn({ name: 'explode-structure-representation-3d', @@ -252,6 +252,29 @@ const ExplodeStructureRepresentation3D = PluginStateTransform.BuiltIn({ } }); +type ColorStructureRepresentation3D = typeof ColorStructureRepresentation3D +const ColorStructureRepresentation3D = PluginStateTransform.BuiltIn({ + name: 'color-structure-representation-3d', + display: 'Color 3D Representation', + from: SO.Molecule.Representation3D, + to: ColorRepresentation3D.Obj, + params: ColorRepresentation3D.Params +})({ + canAutoUpdate() { + return true; + }, + apply({ params }, plugin: PluginContext) { + return new ColorRepresentation3D.Obj(new ColorRepresentation3D.Behavior(plugin, params), { label: `Coloring` }); + }, + update({ b, newParams }) { + return Task.create('Update Coloring', async () => { + const updated = await b.data.update(newParams); + b.label = `Coloring`; + return updated ? StateTransformer.UpdateResult.Updated : StateTransformer.UpdateResult.Unchanged; + }); + } +}); + // export namespace VolumeRepresentation3DHelpers { diff --git a/src/mol-repr/shape/representation.ts b/src/mol-repr/shape/representation.ts index 7ab675168caccdd5eefd64b11a21a91e947a6db6..aa125cfab9e327489d907f93586317d2490b6028 100644 --- a/src/mol-repr/shape/representation.ts +++ b/src/mol-repr/shape/representation.ts @@ -208,11 +208,11 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa const { groupCount, instanceCount } = locationIt for (let i = 0, il = layers.length; i < il; ++i) { - const { loci, color } = layers[i] + const { loci, color, alpha } = layers[i] const apply = (interval: Interval) => { const start = Interval.start(interval) const end = Interval.end(interval) - return applyOverpaintColor(tOverpaint.ref.value.array, start, end, color) + return applyOverpaintColor(tOverpaint.ref.value.array, start, end, color, alpha) } if (isEveryLoci(loci) || (Shape.isLoci(loci) && loci.shape === _shape)) { diff --git a/src/mol-repr/structure/complex-visual.ts b/src/mol-repr/structure/complex-visual.ts index e6785d1f778d21f53a653429df2c8a1771003292..122cbec4ee8174ad638c641bd5aaaff102dee254 100644 --- a/src/mol-repr/structure/complex-visual.ts +++ b/src/mol-repr/structure/complex-visual.ts @@ -214,11 +214,11 @@ export function ComplexVisual<G extends Geometry, P extends ComplexParams & Geom createOverpaint(layers.length ? groupCount * instanceCount : 0, renderObject.values) for (let i = 0, il = layers.length; i < il; ++i) { - const { loci, color } = layers[i] + const { loci, color, alpha } = layers[i] const apply = (interval: Interval) => { const start = Interval.start(interval) const end = Interval.end(interval) - return applyOverpaintColor(tOverpaint.ref.value.array, start, end, color) + return applyOverpaintColor(tOverpaint.ref.value.array, start, end, color, alpha) } if (isEveryLoci(loci) || (Structure.isLoci(loci) && loci.structure === currentStructure)) { diff --git a/src/mol-repr/structure/units-visual.ts b/src/mol-repr/structure/units-visual.ts index fa3c97cdb97a2dea0b6c5a01cc8531bd5d955d39..f7eeba5193a4e08cf138362b039f67ac1e594451 100644 --- a/src/mol-repr/structure/units-visual.ts +++ b/src/mol-repr/structure/units-visual.ts @@ -260,11 +260,11 @@ export function UnitsVisual<G extends Geometry, P extends UnitsParams & Geometry createOverpaint(layers.length ? groupCount * instanceCount : 0, renderObject.values) for (let i = 0, il = layers.length; i < il; ++i) { - const { loci, color } = layers[i] + const { loci, color, alpha } = layers[i] const apply = (interval: Interval) => { const start = Interval.start(interval) const end = Interval.end(interval) - return applyOverpaintColor(tOverpaint.ref.value.array, start, end, color) + return applyOverpaintColor(tOverpaint.ref.value.array, start, end, color, alpha) } if (isEveryLoci(loci) || (Structure.isLoci(loci) && loci.structure === currentStructureGroup.structure)) { diff --git a/src/mol-theme/overpaint.ts b/src/mol-theme/overpaint.ts new file mode 100644 index 0000000000000000000000000000000000000000..b512a08aef9511dd36e11f740ce6c4d8f7dfcb4d --- /dev/null +++ b/src/mol-theme/overpaint.ts @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Loci } from 'mol-model/loci'; +import { Color } from 'mol-util/color'; + +export { Overpaint } +namespace Overpaint { + export interface Layer { + loci: Loci + color: Color + alpha: number + } + export type Layers = Layer[] + + export function areEqual(layersA: Layers, layersB: Layers) { + if (layersA.length === 0 && layersB.length === 0) return true + if (layersA.length !== layersB.length) return false + for (let i = 0, il = layersA.length; i < il; ++i) { + if (layersA[i].alpha !== layersB[i].alpha) return false + if (layersA[i].color !== layersB[i].color) return false + if (!Loci.areEqual(layersA[i].loci, layersB[i].loci)) return false + } + return true + } +} \ No newline at end of file