diff --git a/src/mol-app/ui/transform/ball-and-stick.tsx b/src/mol-app/ui/transform/ball-and-stick.tsx index 9356ca5592cb3d8c3ad9b6824f63a90ceb373d65..ee8abf97a2ab3930a21ed4a3f7ac216d1710a09d 100644 --- a/src/mol-app/ui/transform/ball-and-stick.tsx +++ b/src/mol-app/ui/transform/ball-and-stick.tsx @@ -15,9 +15,10 @@ import { Toggle } from '../controls/common'; import { BallAndStickEntity } from 'mol-view/state/entity'; import { BallAndStickUpdate } from 'mol-view/state/transform' import { StateContext } from 'mol-view/state/context'; -import { ColorTheme } from 'mol-geo/theme'; +import { ColorTheme, SizeTheme } from 'mol-geo/theme'; import { Color, ColorNames } from 'mol-util/color'; import { Slider } from '../controls/slider'; +import { VisualQuality } from 'mol-geo/representation'; export const ColorThemeInfo = { 'atom-index': {}, @@ -34,9 +35,17 @@ interface BallAndStickState { flatShaded: boolean colorTheme: ColorTheme colorValue: Color + sizeTheme: SizeTheme visible: boolean alpha: number depthMask: boolean + useFog: boolean + quality: VisualQuality + linkScale: number + linkSpacing: number + linkRadius: number + radialSegments: number + detail: number } export class BallAndStick extends View<Controller<any>, BallAndStickState, { transform: BallAndStickUpdate, entity: BallAndStickEntity, ctx: StateContext }> { @@ -46,9 +55,21 @@ export class BallAndStick extends View<Controller<any>, BallAndStickState, { tra flatShaded: false, colorTheme: { name: 'element-symbol' } as ColorTheme, colorValue: 0x000000, + sizeTheme: { name: 'uniform' } as SizeTheme, visible: true, alpha: 1, - depthMask: true + depthMask: true, + useFog: true, + quality: 'auto' as VisualQuality, + linkScale: 0.4, + linkSpacing: 1, + linkRadius: 0.25, + radialSegments: 16, + detail: 1 + } + + componentWillMount() { + this.setState({ ...this.state, ...this.props.entity.value.props }) } update(state?: Partial<BallAndStickState>) { @@ -61,6 +82,10 @@ export class BallAndStick extends View<Controller<any>, BallAndStickState, { tra render() { const { transform } = this.props + const qualityOptions = ['auto', 'custom', 'highest', 'high', 'medium', 'low', 'lowest'].map((name, idx) => { + return <option key={name} value={name}>{name}</option> + }) + const colorThemeOptions = Object.keys(ColorThemeInfo).map((name, idx) => { return <option key={name} value={name}>{name}</option> }) @@ -82,6 +107,16 @@ export class BallAndStick extends View<Controller<any>, BallAndStickState, { tra <div className='molstar-panel-body'> <div> <div className='molstar-control-row molstar-options-group'> + <span>Quality</span> + <div> + <select + className='molstar-form-control' + value={this.state.quality} + onChange={(e) => this.update({ quality: e.target.value as VisualQuality })} + > + {qualityOptions} + </select> + </div> <span>Color theme</span> <div> <select diff --git a/src/mol-app/ui/transform/spacefill.tsx b/src/mol-app/ui/transform/spacefill.tsx index d613dbaa4a2c39bb4a4dd8a105462c726281ff8c..3d6adbe7c4337008fb1c8737836790b774c97226 100644 --- a/src/mol-app/ui/transform/spacefill.tsx +++ b/src/mol-app/ui/transform/spacefill.tsx @@ -15,9 +15,10 @@ import { Toggle } from '../controls/common'; import { SpacefillEntity } from 'mol-view/state/entity'; import { SpacefillUpdate } from 'mol-view/state/transform' import { StateContext } from 'mol-view/state/context'; -import { ColorTheme } from 'mol-geo/theme'; +import { ColorTheme, SizeTheme } from 'mol-geo/theme'; import { Color, ColorNames } from 'mol-util/color'; import { Slider } from '../controls/slider'; +import { VisualQuality } from 'mol-geo/representation'; export const ColorThemeInfo = { 'atom-index': {}, @@ -35,9 +36,12 @@ interface SpacefillState { detail: number colorTheme: ColorTheme colorValue: Color + sizeTheme: SizeTheme visible: boolean alpha: number depthMask: boolean + useFog: boolean + quality: VisualQuality } export class Spacefill extends View<Controller<any>, SpacefillState, { transform: SpacefillUpdate, entity: SpacefillEntity, ctx: StateContext }> { @@ -48,12 +52,20 @@ export class Spacefill extends View<Controller<any>, SpacefillState, { transform detail: 2, colorTheme: { name: 'element-symbol' } as ColorTheme, colorValue: 0x000000, + sizeTheme: { name: 'uniform' } as SizeTheme, visible: true, alpha: 1, - depthMask: true + depthMask: true, + useFog: true, + quality: 'auto' as VisualQuality + } + + componentWillMount() { + this.setState({ ...this.state, ...this.props.entity.value.props }) } update(state?: Partial<SpacefillState>) { + console.log(state) const { transform, entity, ctx } = this.props const newState = { ...this.state, ...state } this.setState(newState) @@ -63,6 +75,10 @@ export class Spacefill extends View<Controller<any>, SpacefillState, { transform render() { const { transform } = this.props + const qualityOptions = ['auto', 'custom', 'highest', 'high', 'medium', 'low', 'lowest'].map((name, idx) => { + return <option key={name} value={name}>{name}</option> + }) + const sphereDetailOptions = [0, 1, 2, 3].map((value, idx) => { return <option key={value} value={value}>{value.toString()}</option> }) @@ -87,6 +103,18 @@ export class Spacefill extends View<Controller<any>, SpacefillState, { transform </div> <div className='molstar-panel-body'> <div> + <div className='molstar-control-row molstar-options-group'> + <span>Quality</span> + <div> + <select + className='molstar-form-control' + value={this.state.quality} + onChange={(e) => this.update({ quality: e.target.value as VisualQuality })} + > + {qualityOptions} + </select> + </div> + </div> <div className='molstar-control-row molstar-options-group'> <span>Sphere detail</span> <div> diff --git a/src/mol-geo/representation/index.ts b/src/mol-geo/representation/index.ts index 8318003e342708ce032731ac252aa6e2c6defc68..aa17e39ad6b079e92dde5a4d6ddf5739f751c747 100644 --- a/src/mol-geo/representation/index.ts +++ b/src/mol-geo/representation/index.ts @@ -10,10 +10,13 @@ import { PickingId } from '../util/picking'; import { Loci } from 'mol-model/loci'; import { MarkerAction } from '../util/marker-data'; +export type VisualQuality = 'custom' | 'auto' | 'highest' | 'high' | 'medium' | 'low' | 'lowest' + export interface RepresentationProps {} export interface Representation<D, P extends RepresentationProps = {}> { readonly renderObjects: ReadonlyArray<RenderObject> + readonly props: Readonly<P> create: (data: D, props?: P) => Task<void> update: (props: P) => Task<void> getLoci: (pickingId: PickingId) => Loci diff --git a/src/mol-geo/representation/structure/ball-and-stick.ts b/src/mol-geo/representation/structure/ball-and-stick.ts index c5a4755ef2fb157c3e9514af94b80418b13d4aef..e2b9b2f3c786bf15b49f3d5c8f7f5347fd80e15c 100644 --- a/src/mol-geo/representation/structure/ball-and-stick.ts +++ b/src/mol-geo/representation/structure/ball-and-stick.ts @@ -32,6 +32,9 @@ export function BallAndStickRepresentation(): StructureRepresentation<BallAndSti // return linkRepr.renderObjects return [ ...elmementRepr.renderObjects, ...linkRepr.renderObjects ] }, + get props() { + return { ...elmementRepr.props, ...linkRepr.props } + }, create: (structure: Structure, props: BallAndStickProps = {} as BallAndStickProps) => { const p = Object.assign({}, DefaultBallAndStickProps, props) return Task.create('Creating BallAndStickRepresentation', async ctx => { @@ -40,9 +43,10 @@ export function BallAndStickRepresentation(): StructureRepresentation<BallAndSti }) }, update: (props: BallAndStickProps) => { + const p = Object.assign({}, props) return Task.create('Updating BallAndStickRepresentation', async ctx => { - await elmementRepr.update(props).runInContext(ctx) - await linkRepr.update(props).runInContext(ctx) + await elmementRepr.update(p).runInContext(ctx) + await linkRepr.update(p).runInContext(ctx) }) }, getLoci: (pickingId: PickingId) => { diff --git a/src/mol-geo/representation/structure/index.ts b/src/mol-geo/representation/structure/index.ts index 4101ec84cabc268a79814d439c3953d49d978e8f..37defba37882ce7208f97fc5edf357348957abfb 100644 --- a/src/mol-geo/representation/structure/index.ts +++ b/src/mol-geo/representation/structure/index.ts @@ -8,17 +8,65 @@ import { Structure, StructureSymmetry, Unit } from 'mol-model/structure'; import { Task } from 'mol-task' import { RenderObject } from 'mol-gl/render-object'; -import { Representation, RepresentationProps, Visual } from '..'; +import { Representation, RepresentationProps, Visual, VisualQuality } from '..'; import { ColorTheme, SizeTheme } from '../../theme'; import { PickingId } from '../../util/picking'; import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci'; import { MarkerAction } from '../../util/marker-data'; +import { defaults } from 'mol-util'; export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<Unit.SymmetryGroup, P> { } export interface StructureVisual<P extends RepresentationProps = {}> extends Visual<Structure, P> { } export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> { } +interface QualityProps { + quality: VisualQuality + detail: number + radialSegments: number +} + +function getQualityProps(props: Partial<QualityProps>, structure: Structure) { + const quality = defaults(props.quality, 'auto' as VisualQuality) + let detail = 1 + let radialSegments = 12 + + switch (quality) { + case 'highest': + detail = 3 + radialSegments = 36 + break + case 'high': + detail = 2 + radialSegments = 24 + break + case 'medium': + detail = 1 + radialSegments = 12 + break + case 'low': + detail = 0 + radialSegments = 5 + break + case 'lowest': + detail = 0 + radialSegments = 3 + break + case 'auto': + // TODO + break + case 'custom': + detail = defaults(props.detail, 1) + radialSegments = defaults(props.radialSegments, 12) + break + } + + return { + detail, + radialSegments + } +} + export const DefaultStructureProps = { colorTheme: { name: 'instance-index' } as ColorTheme, sizeTheme: { name: 'physical' } as SizeTheme, @@ -27,6 +75,7 @@ export const DefaultStructureProps = { doubleSided: false, depthMask: true, useFog: true, + quality: 'auto' as VisualQuality } export type StructureProps = Partial<typeof DefaultStructureProps> @@ -39,7 +88,8 @@ export function StructureRepresentation<P extends StructureProps>(unitsVisualCto let _groups: ReadonlyArray<Unit.SymmetryGroup> function create(structure: Structure, props: P = {} as P) { - _props = Object.assign({}, DefaultStructureProps, _props, props) + _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, structure)) + console.log('create struct', (_props as any).detail, (_props as any).radialSegments) return Task.create('Creating StructureRepresentation', async ctx => { if (!_structure) { @@ -103,7 +153,10 @@ export function StructureRepresentation<P extends StructureProps>(unitsVisualCto function update(props: P) { return Task.create('Updating StructureRepresentation', async ctx => { - _props = Object.assign({}, DefaultStructureProps, _props, props) + console.log(getQualityProps(props, _structure)) + _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, _structure)) + + console.log('update struct', (_props as any).detail, (_props as any).radialSegments) unitsVisuals.forEach(async ({ visual, group }) => { if (!await visual.update(ctx, _props)) { @@ -153,6 +206,9 @@ export function StructureRepresentation<P extends StructureProps>(unitsVisualCto if (structureVisual) renderObjects.push(...structureVisual.renderObjects) return renderObjects }, + get props() { + return _props + }, create, update, getLoci, diff --git a/src/mol-geo/representation/structure/visual/inter-unit-link-cylinder.ts b/src/mol-geo/representation/structure/visual/inter-unit-link-cylinder.ts index fa76a9e30682fb81d21be816edc22eb1c5f830ea..4c4b69eb6ff11108b870c046dab510d7a14a146a 100644 --- a/src/mol-geo/representation/structure/visual/inter-unit-link-cylinder.ts +++ b/src/mol-geo/representation/structure/visual/inter-unit-link-cylinder.ts @@ -118,7 +118,7 @@ export function InterUnitLinkVisual(): StructureVisual<InterUnitLinkProps> { async update(ctx: RuntimeContext, props: InterUnitLinkProps) { const newProps = Object.assign({}, currentProps, props) - if (!cylinders) return false + if (!cylinders || currentProps.radialSegments !== newProps.radialSegments) return false // TODO ValueCell.updateIfChanged(cylinders.values.uAlpha, newProps.alpha) @@ -129,7 +129,7 @@ export function InterUnitLinkVisual(): StructureVisual<InterUnitLinkProps> { cylinders.state.visible = newProps.visible cylinders.state.depthMask = newProps.depthMask - return true + return false }, getLoci(pickingId: PickingId) { return getLinkLoci(pickingId, currentStructure, cylinders.id) diff --git a/src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts b/src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts index 15564c2cf637ee2879c72bb557fa48e7bd8fcab7..d4652f10d02b256f788626a564f6211d06d09b4b 100644 --- a/src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts +++ b/src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts @@ -135,7 +135,7 @@ export function IntraUnitLinkVisual(): UnitsVisual<IntraUnitLinkProps> { async update(ctx: RuntimeContext, props: IntraUnitLinkProps) { const newProps = Object.assign({}, currentProps, props) - if (!cylinders) return false + if (!cylinders || currentProps.radialSegments !== newProps.radialSegments) return false // TODO ValueCell.updateIfChanged(cylinders.values.uAlpha, newProps.alpha) diff --git a/src/mol-geo/representation/volume/index.ts b/src/mol-geo/representation/volume/index.ts index 17da82eee5bc513a614f30de71c5398ee265ee9e..f45b559ca306c07ece22256388ba25e9f12d2c0a 100644 --- a/src/mol-geo/representation/volume/index.ts +++ b/src/mol-geo/representation/volume/index.ts @@ -19,8 +19,10 @@ export interface VolumeRepresentation<P extends RepresentationProps = {}> extend export function VolumeRepresentation<P>(visualCtor: (volumeData: VolumeData) => VolumeVisual<P>): VolumeRepresentation<P> { const renderObjects: RenderObject[] = [] let _volumeData: VolumeData + let _props: P function create(volumeData: VolumeData, props: P = {} as P) { + _props = props return Task.create('VolumeRepresentation.create', async ctx => { _volumeData = volumeData const visual = visualCtor(_volumeData) @@ -28,13 +30,18 @@ export function VolumeRepresentation<P>(visualCtor: (volumeData: VolumeData) => renderObjects.push(...visual.renderObjects) }); } - + function update(props: P) { return Task.create('VolumeRepresentation.update', async ctx => {}) } return { - renderObjects, + get renderObjects () { + return renderObjects + }, + get props () { + return _props + }, create, update, getLoci(pickingId: PickingId) { diff --git a/src/mol-util/index.ts b/src/mol-util/index.ts index 17612ea0b256f32e32977ba1a7cb2496ace9e748..9c1aa24bfe624cbcd813ffdbf42bcbbcf3cfc6ef 100644 --- a/src/mol-util/index.ts +++ b/src/mol-util/index.ts @@ -94,7 +94,7 @@ export function shallowEqual<T>(a: T, b: T) { } /** Returns `value` if not `undefined`, otherwise returns `defaultValue` */ -export function defaults(value: any, defaultValue: any) { +export function defaults<T>(value: T | undefined, defaultValue: T): T { return value !== undefined ? value : defaultValue } diff --git a/src/mol-view/stage.ts b/src/mol-view/stage.ts index b9afd01aaf4d647893092389258bbd3b35f3dc75..097f79f2c382769ad6cd338088d3b09a80bfecd9 100644 --- a/src/mol-view/stage.ts +++ b/src/mol-view/stage.ts @@ -15,16 +15,15 @@ import { BallAndStickProps } from 'mol-geo/representation/structure/ball-and-sti const spacefillProps: SpacefillProps = { doubleSided: true, - detail: 0, - colorTheme: { name: 'atom-index' } + colorTheme: { name: 'atom-index' }, + quality: 'medium' } const ballAndStickProps: BallAndStickProps = { doubleSided: true, - detail: 1, - radialSegments: 8, colorTheme: { name: 'chain-id' }, sizeTheme: { name: 'uniform', value: 0.25 }, + quality: 'medium' } export class Stage { @@ -43,8 +42,8 @@ export class Stage { // this.loadPdbid('1jj2') // this.loadPdbid('4umt') // ligand has bond with order 3 // this.loadPdbid('1crn') // small - this.loadPdbid('1blu') // metal coordination - // this.loadPdbid('3pqr') // inter unit bonds + // this.loadPdbid('1blu') // metal coordination + this.loadPdbid('3pqr') // inter unit bonds // this.loadPdbid('4v5a') // ribosome // this.loadMmcifUrl(`../../examples/1cbs_full.bcif`) }