diff --git a/package-lock.json b/package-lock.json index d4f31bbfd4474ef3cc6a11381c23dd4e322cf34f..789e7451fd84970635acce32117184ecab4dc72f 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/package.json b/package.json index 08a63d64fc9585b9146056d2d1a05965e0daa44c..6c9e54c63b30edb2e674afef76d43c1f4a5b9e8e 100644 --- a/package.json +++ b/package.json @@ -70,10 +70,10 @@ "@types/benchmark": "^1.0.31", "@types/compression": "0.0.36", "@types/express": "^4.16.0", - "@types/jest": "^23.1.1", - "@types/node": "~10.3.4", + "@types/jest": "^23.1.2", + "@types/node": "~10.5.0", "@types/node-fetch": "^2.1.1", - "@types/react": "^16.4.0", + "@types/react": "^16.4.3", "@types/react-dom": "^16.0.6", "benchmark": "^2.1.4", "copyfiles": "^2.0.0", @@ -84,7 +84,7 @@ "file-loader": "^1.1.11", "glslify-import": "^3.1.0", "glslify-loader": "^1.0.2", - "jest": "^23.1.0", + "jest": "^23.2.0", "jest-raw-loader": "^1.0.1", "node-sass": "^4.9.0", "raw-loader": "^0.5.1", @@ -94,9 +94,9 @@ "ts-jest": "^22.4.6", "tslint": "^5.10.0", "typescript": "^2.9.2", - "uglify-js": "^3.4.0", + "uglify-js": "^3.4.2", "util.promisify": "^1.0.0", - "webpack": "^4.12.0", + "webpack": "^4.12.2", "webpack-cli": "^3.0.8" }, "dependencies": { diff --git a/src/apps/schema-generator/util/cif-dic.ts b/src/apps/schema-generator/util/cif-dic.ts index 718c9348394ba52907534642303be0382640ad1e..ca190ae4ad17f83374f7d53a1a2a94e2ea5cb0c9 100644 --- a/src/apps/schema-generator/util/cif-dic.ts +++ b/src/apps/schema-generator/util/cif-dic.ts @@ -178,6 +178,10 @@ const SPACE_SEPARATED_LIST_FIELDS = [ '_pdbx_soln_scatter.data_analysis_software_list', // SCTPL5 GNOM ]; +const SEMICOLON_SEPARATED_LIST_FIELDS = [ + '_chem_comp.pdbx_synonyms' // GLYCERIN; PROPANE-1,2,3-TRIOL +] + export function generateSchema (frames: CifFrame[]) { const schema: Database = {} @@ -250,6 +254,9 @@ export function generateSchema (frames: CifFrame[]) { } else if (SPACE_SEPARATED_LIST_FIELDS.includes(d.header)) { fieldType = { 'list': [ 'str', ' ' ] }; console.log(`space separated: ${d.header}`) + } else if (SEMICOLON_SEPARATED_LIST_FIELDS.includes(d.header)) { + fieldType = { 'list': [ 'str', ';' ] }; + console.log(`space separated: ${d.header}`) } } fields[itemName] = fieldType diff --git a/src/apps/structure-info/model.ts b/src/apps/structure-info/model.ts index 2cc19a45613210f720cd19807d81924d8ff7cf6c..17f1b6373892df00200857c438245b3dace4b97e 100644 --- a/src/apps/structure-info/model.ts +++ b/src/apps/structure-info/model.ts @@ -238,7 +238,6 @@ parser.addArgument(['--file', '-f'], { help: 'filename' }); parser.addArgument(['--models'], { help: 'print models info', action: 'storeTrue' }); parser.addArgument(['--seq'], { help: 'print sequence', action: 'storeTrue' }); -parser.addArgument(['--ihm'], { help: 'print IHM', action: 'storeTrue' }); parser.addArgument(['--units'], { help: 'print units', action: 'storeTrue' }); parser.addArgument(['--sym'], { help: 'print symmetry', action: 'storeTrue' }); parser.addArgument(['--rings'], { help: 'print rings', action: 'storeTrue' }); diff --git a/src/mol-app/ui/controls/slider.tsx b/src/mol-app/ui/controls/slider.tsx index 4aac7c3818af58383b4e21ca98c21bbd0aed1469..23a2387bb66d2c7af3a0d57509c737bb12f273e3 100644 --- a/src/mol-app/ui/controls/slider.tsx +++ b/src/mol-app/ui/controls/slider.tsx @@ -206,6 +206,12 @@ export class SliderBase extends React.Component<SliderBaseProps, SliderBaseState private sliderElement: HTMLElement | undefined = void 0; private handleElements: (HTMLElement | undefined)[] = []; + state: SliderBaseState = { + handle: null, + recent: 0, + bounds: [0, 0], + }; + constructor(props: SliderBaseProps) { super(props); diff --git a/src/mol-app/ui/entity/tree.tsx b/src/mol-app/ui/entity/tree.tsx index 755b2c1008738432a049b0c5e40d7a36c29f6534..8c976f5cb366d9daa0e0d50bb10bce9912926c3e 100644 --- a/src/mol-app/ui/entity/tree.tsx +++ b/src/mol-app/ui/entity/tree.tsx @@ -13,7 +13,7 @@ import { View } from '../view'; import { EntityTreeController } from '../../controller/entity/tree'; import { Controller } from '../../controller/controller'; import { AnyEntity, RootEntity } from 'mol-view/state/entity'; -import { AnyTransform, SpacefillUpdate, UrlToData, DataToCif, FileToData, CifToMmcif, MmcifToModel, ModelToStructure, StructureToSpacefill, MmcifFileToSpacefill, StructureCenter, StructureToBallAndStick, BallAndStickUpdate } from 'mol-view/state/transform'; +import { AnyTransform, SpacefillUpdate, UrlToData, DataToCif, FileToData, CifToMmcif, MmcifToModel, ModelToStructure, StructureToSpacefill, MmcifFileToSpacefill, StructureCenter, StructureToBallAndStick, DistanceRestraintUpdate, CartoonUpdate, BallAndStickUpdate, BackboneUpdate } from 'mol-view/state/transform'; function getTransforms(entity: AnyEntity): AnyTransform[] { const transforms: AnyTransform[] = [] @@ -48,6 +48,15 @@ function getTransforms(entity: AnyEntity): AnyTransform[] { case 'ballandstick': transforms.push(BallAndStickUpdate) break; + case 'distancerestraint': + transforms.push(DistanceRestraintUpdate) + break; + case 'backbone': + transforms.push(BackboneUpdate) + break; + case 'cartoon': + transforms.push(CartoonUpdate) + break; } return transforms } diff --git a/src/mol-app/ui/transform/backbone.tsx b/src/mol-app/ui/transform/backbone.tsx new file mode 100644 index 0000000000000000000000000000000000000000..76bb3475c7ed50883e426c557bff7f326500518f --- /dev/null +++ b/src/mol-app/ui/transform/backbone.tsx @@ -0,0 +1,243 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import * as React from 'react' + +import { View } from '../view'; +import { Controller } from '../../controller/controller'; +import { Toggle } from '../controls/common'; +import { BackboneEntity } from 'mol-view/state/entity'; +import { BackboneUpdate } from 'mol-view/state/transform' +import { StateContext } from 'mol-view/state/context'; +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/util'; +import { Unit } from 'mol-model/structure'; + +export const ColorThemeInfo = { + 'atom-index': {}, + 'chain-id': {}, + 'element-symbol': {}, + 'instance-index': {}, + 'uniform': {} +} +export type ColorThemeInfo = keyof typeof ColorThemeInfo + +interface BackboneState { + doubleSided: boolean + flipSided: boolean + flatShaded: boolean + detail: number + colorTheme: ColorTheme + colorValue: Color + sizeTheme: SizeTheme + visible: boolean + alpha: number + depthMask: boolean + useFog: boolean + quality: VisualQuality + unitKinds: Unit.Kind[] +} + +export class Backbone extends View<Controller<any>, BackboneState, { transform: BackboneUpdate, entity: BackboneEntity, ctx: StateContext }> { + state = { + doubleSided: true, + flipSided: false, + flatShaded: false, + detail: 2, + colorTheme: { name: 'element-symbol' } as ColorTheme, + colorValue: 0x000000, + sizeTheme: { name: 'uniform' } as SizeTheme, + visible: true, + alpha: 1, + depthMask: true, + useFog: true, + quality: 'auto' as VisualQuality, + unitKinds: [] as Unit.Kind[] + } + + componentWillMount() { + this.setState({ ...this.state, ...this.props.entity.value.props }) + } + + update(state?: Partial<BackboneState>) { + console.log(state) + const { transform, entity, ctx } = this.props + const newState = { ...this.state, ...state } + this.setState(newState) + transform.apply(ctx, entity, newState) + } + + 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> + }) + + const colorThemeOptions = Object.keys(ColorThemeInfo).map((name, idx) => { + return <option key={name} value={name}>{name}</option> + }) + + const colorValueOptions = Object.keys(ColorNames).map((name, idx) => { + return <option key={name} value={(ColorNames as any)[name]}>{name}</option> + }) + + return <div className='molstar-transformer-wrapper'> + <div className='molstar-panel molstar-control molstar-transformer molstar-panel-expanded'> + <div className='molstar-panel-header'> + <button + className='molstar-btn molstar-btn-link molstar-panel-expander' + onClick={() => this.update()} + > + <span>[{transform.kind}] {transform.inputKind} -> {transform.outputKind}</span> + </button> + </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> + <select + className='molstar-form-control' + value={this.state.detail} + onChange={(e) => this.update({ detail: parseInt(e.target.value) })} + > + {sphereDetailOptions} + </select> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <span>Color theme</span> + <div> + <select + className='molstar-form-control' + value={this.state.colorTheme.name} + onChange={(e) => { + const colorThemeName = e.target.value as ColorThemeInfo + if (colorThemeName === 'uniform') { + this.update({ + colorTheme: { + name: colorThemeName, + value: this.state.colorValue + } + }) + } else { + this.update({ + colorTheme: { name: colorThemeName } + }) + } + }} + > + {colorThemeOptions} + </select> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <span>Color value</span> + <div> + <select + className='molstar-form-control' + value={this.state.colorValue} + onChange={(e) => { + const colorValue = parseInt(e.target.value) + this.update({ + colorTheme: { + name: 'uniform', + value: colorValue + }, + colorValue + }) + }} + > + {colorValueOptions} + </select> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Toggle + value={this.state.visible} + label='Visibility' + onChange={value => this.update({ visible: value })} + /> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Toggle + value={this.state.depthMask} + label='Depth write' + onChange={value => this.update({ depthMask: value })} + /> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Toggle + value={this.state.doubleSided} + label='Double sided' + onChange={value => this.update({ doubleSided: value })} + /> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Toggle + value={this.state.flipSided} + label='Flip sided' + onChange={value => this.update({ flipSided: value })} + /> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Toggle + value={this.state.flatShaded} + label='Flat shaded' + onChange={value => this.update({ flatShaded: value })} + /> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Slider + value={this.state.alpha} + label='Opacity' + min={0} + max={1} + step={0.01} + callOnChangeWhileSliding={true} + onChange={value => this.update({ alpha: value })} + /> + </div> + </div> + </div> + </div> + </div> + </div>; + } +} \ No newline at end of file diff --git a/src/mol-app/ui/transform/ball-and-stick.tsx b/src/mol-app/ui/transform/ball-and-stick.tsx index d597e51bb0be45c35b66daa83cf8d7135c1797b1..9ed5c5e35c133e5193d4e33c0e821cbce760a607 100644 --- a/src/mol-app/ui/transform/ball-and-stick.tsx +++ b/src/mol-app/ui/transform/ball-and-stick.tsx @@ -12,8 +12,8 @@ import * as React from 'react' import { View } from '../view'; import { Controller } from '../../controller/controller'; import { Toggle } from '../controls/common'; -import { BallAndStickEntity } from 'mol-view/state/entity'; -import { BallAndStickUpdate } from 'mol-view/state/transform' +import { DistanceRestraintEntity } from 'mol-view/state/entity'; +import { DistanceRestraintUpdate } from 'mol-view/state/transform' import { StateContext } from 'mol-view/state/context'; import { ColorTheme, SizeTheme } from 'mol-geo/theme'; import { Color, ColorNames } from 'mol-util/color'; @@ -50,7 +50,7 @@ interface BallAndStickState { unitKinds: Unit.Kind[] } -export class BallAndStick extends View<Controller<any>, BallAndStickState, { transform: BallAndStickUpdate, entity: BallAndStickEntity, ctx: StateContext }> { +export class BallAndStick extends View<Controller<any>, BallAndStickState, { transform: DistanceRestraintUpdate, entity: DistanceRestraintEntity, ctx: StateContext }> { state = { doubleSided: true, flipSided: false, diff --git a/src/mol-app/ui/transform/cartoon.tsx b/src/mol-app/ui/transform/cartoon.tsx new file mode 100644 index 0000000000000000000000000000000000000000..da5da45e7f27e5feb50f7ef2932637e82d3eb35d --- /dev/null +++ b/src/mol-app/ui/transform/cartoon.tsx @@ -0,0 +1,243 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import * as React from 'react' + +import { View } from '../view'; +import { Controller } from '../../controller/controller'; +import { Toggle } from '../controls/common'; +import { CartoonEntity } from 'mol-view/state/entity'; +import { CartoonUpdate } from 'mol-view/state/transform' +import { StateContext } from 'mol-view/state/context'; +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/util'; +import { Unit } from 'mol-model/structure'; + +export const ColorThemeInfo = { + 'atom-index': {}, + 'chain-id': {}, + 'element-symbol': {}, + 'instance-index': {}, + 'uniform': {} +} +export type ColorThemeInfo = keyof typeof ColorThemeInfo + +interface CartoonState { + doubleSided: boolean + flipSided: boolean + flatShaded: boolean + detail: number + colorTheme: ColorTheme + colorValue: Color + sizeTheme: SizeTheme + visible: boolean + alpha: number + depthMask: boolean + useFog: boolean + quality: VisualQuality + unitKinds: Unit.Kind[] +} + +export class Cartoon extends View<Controller<any>, CartoonState, { transform: CartoonUpdate, entity: CartoonEntity, ctx: StateContext }> { + state = { + doubleSided: true, + flipSided: false, + flatShaded: false, + detail: 2, + colorTheme: { name: 'element-symbol' } as ColorTheme, + colorValue: 0x000000, + sizeTheme: { name: 'uniform' } as SizeTheme, + visible: true, + alpha: 1, + depthMask: true, + useFog: true, + quality: 'auto' as VisualQuality, + unitKinds: [] as Unit.Kind[] + } + + componentWillMount() { + this.setState({ ...this.state, ...this.props.entity.value.props }) + } + + update(state?: Partial<CartoonState>) { + console.log(state) + const { transform, entity, ctx } = this.props + const newState = { ...this.state, ...state } + this.setState(newState) + transform.apply(ctx, entity, newState) + } + + 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> + }) + + const colorThemeOptions = Object.keys(ColorThemeInfo).map((name, idx) => { + return <option key={name} value={name}>{name}</option> + }) + + const colorValueOptions = Object.keys(ColorNames).map((name, idx) => { + return <option key={name} value={(ColorNames as any)[name]}>{name}</option> + }) + + return <div className='molstar-transformer-wrapper'> + <div className='molstar-panel molstar-control molstar-transformer molstar-panel-expanded'> + <div className='molstar-panel-header'> + <button + className='molstar-btn molstar-btn-link molstar-panel-expander' + onClick={() => this.update()} + > + <span>[{transform.kind}] {transform.inputKind} -> {transform.outputKind}</span> + </button> + </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> + <select + className='molstar-form-control' + value={this.state.detail} + onChange={(e) => this.update({ detail: parseInt(e.target.value) })} + > + {sphereDetailOptions} + </select> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <span>Color theme</span> + <div> + <select + className='molstar-form-control' + value={this.state.colorTheme.name} + onChange={(e) => { + const colorThemeName = e.target.value as ColorThemeInfo + if (colorThemeName === 'uniform') { + this.update({ + colorTheme: { + name: colorThemeName, + value: this.state.colorValue + } + }) + } else { + this.update({ + colorTheme: { name: colorThemeName } + }) + } + }} + > + {colorThemeOptions} + </select> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <span>Color value</span> + <div> + <select + className='molstar-form-control' + value={this.state.colorValue} + onChange={(e) => { + const colorValue = parseInt(e.target.value) + this.update({ + colorTheme: { + name: 'uniform', + value: colorValue + }, + colorValue + }) + }} + > + {colorValueOptions} + </select> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Toggle + value={this.state.visible} + label='Visibility' + onChange={value => this.update({ visible: value })} + /> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Toggle + value={this.state.depthMask} + label='Depth write' + onChange={value => this.update({ depthMask: value })} + /> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Toggle + value={this.state.doubleSided} + label='Double sided' + onChange={value => this.update({ doubleSided: value })} + /> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Toggle + value={this.state.flipSided} + label='Flip sided' + onChange={value => this.update({ flipSided: value })} + /> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Toggle + value={this.state.flatShaded} + label='Flat shaded' + onChange={value => this.update({ flatShaded: value })} + /> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Slider + value={this.state.alpha} + label='Opacity' + min={0} + max={1} + step={0.01} + callOnChangeWhileSliding={true} + onChange={value => this.update({ alpha: value })} + /> + </div> + </div> + </div> + </div> + </div> + </div>; + } +} \ No newline at end of file diff --git a/src/mol-app/ui/transform/distance-restraint.tsx b/src/mol-app/ui/transform/distance-restraint.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cf74c3992c5b83c9c9fc57b21b1dc3743c69019f --- /dev/null +++ b/src/mol-app/ui/transform/distance-restraint.tsx @@ -0,0 +1,234 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import * as React from 'react' + +import { View } from '../view'; +import { Controller } from '../../controller/controller'; +import { Toggle } from '../controls/common'; +import { DistanceRestraintEntity } from 'mol-view/state/entity'; +import { DistanceRestraintUpdate } from 'mol-view/state/transform' +import { StateContext } from 'mol-view/state/context'; +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/util'; +import { Unit } from 'mol-model/structure'; + +export const ColorThemeInfo = { + 'atom-index': {}, + 'chain-id': {}, + 'element-symbol': {}, + 'instance-index': {}, + 'uniform': {} +} +export type ColorThemeInfo = keyof typeof ColorThemeInfo + +interface DistanceRestraintState { + doubleSided: boolean + flipSided: boolean + 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 + unitKinds: Unit.Kind[] +} + +export class DistanceRestraint extends View<Controller<any>, DistanceRestraintState, { transform: DistanceRestraintUpdate, entity: DistanceRestraintEntity, ctx: StateContext }> { + state = { + doubleSided: true, + flipSided: false, + flatShaded: false, + colorTheme: { name: 'element-symbol' } as ColorTheme, + colorValue: 0x000000, + sizeTheme: { name: 'uniform' } as SizeTheme, + visible: true, + alpha: 1, + depthMask: true, + useFog: true, + quality: 'auto' as VisualQuality, + linkScale: 0.4, + linkSpacing: 1, + linkRadius: 0.25, + radialSegments: 16, + detail: 1, + unitKinds: [] as Unit.Kind[] + } + + componentWillMount() { + this.setState({ ...this.state, ...this.props.entity.value.props }) + } + + update(state?: Partial<DistanceRestraintState>) { + const { transform, entity, ctx } = this.props + const newState = { ...this.state, ...state } + this.setState(newState) + transform.apply(ctx, entity, newState) + } + + 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> + }) + + const colorValueOptions = Object.keys(ColorNames).map((name, idx) => { + return <option key={name} value={(ColorNames as any)[name]}>{name}</option> + }) + + return <div className='molstar-transformer-wrapper'> + <div className='molstar-panel molstar-control molstar-transformer molstar-panel-expanded'> + <div className='molstar-panel-header'> + <button + className='molstar-btn molstar-btn-link molstar-panel-expander' + onClick={() => this.update()} + > + <span>[{transform.kind}] {transform.inputKind} -> {transform.outputKind}</span> + </button> + </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>Color theme</span> + <div> + <select + className='molstar-form-control' + value={this.state.colorTheme.name} + onChange={(e) => { + const colorThemeName = e.target.value as ColorThemeInfo + if (colorThemeName === 'uniform') { + this.update({ + colorTheme: { + name: colorThemeName, + value: this.state.colorValue + } + }) + } else { + this.update({ + colorTheme: { name: colorThemeName } + }) + } + }} + > + {colorThemeOptions} + </select> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <span>Color value</span> + <div> + <select + className='molstar-form-control' + value={this.state.colorValue} + onChange={(e) => { + const colorValue = parseInt(e.target.value) + this.update({ + colorTheme: { + name: 'uniform', + value: colorValue + }, + colorValue + }) + }} + > + {colorValueOptions} + </select> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Toggle + value={this.state.visible} + label='Visibility' + onChange={value => this.update({ visible: value })} + /> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Toggle + value={this.state.depthMask} + label='Depth write' + onChange={value => this.update({ depthMask: value })} + /> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Toggle + value={this.state.doubleSided} + label='Double sided' + onChange={value => this.update({ doubleSided: value })} + /> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Toggle + value={this.state.flipSided} + label='Flip sided' + onChange={value => this.update({ flipSided: value })} + /> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Toggle + value={this.state.flatShaded} + label='Flat shaded' + onChange={value => this.update({ flatShaded: value })} + /> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Slider + value={this.state.alpha} + label='Opacity' + min={0} + max={1} + step={0.01} + callOnChangeWhileSliding={true} + onChange={value => this.update({ alpha: value })} + /> + </div> + </div> + </div> + </div> + </div> + </div>; + } +} \ No newline at end of file diff --git a/src/mol-app/ui/transform/file-loader.tsx b/src/mol-app/ui/transform/file-loader.tsx index a88b13f9a5dce132ad90f15923812420459d0b85..c486e4a12de88d56191c7a21a8089a993f559d2c 100644 --- a/src/mol-app/ui/transform/file-loader.tsx +++ b/src/mol-app/ui/transform/file-loader.tsx @@ -9,18 +9,60 @@ import { View } from '../view'; import { FileInput } from '../controls/common'; import { TransformListController } from '../../controller/transform/list'; import { FileEntity } from 'mol-view/state/entity'; -import { MmcifFileToSpacefill } from 'mol-view/state/transform'; +import { MmcifFileToModel, ModelToStructure, StructureToBallAndStick, StructureToSpacefill, StructureToDistanceRestraint, StructureToBackbone } from 'mol-view/state/transform'; import { StateContext } from 'mol-view/state/context'; +import { SpacefillProps } from 'mol-geo/representation/structure/spacefill'; +import { BallAndStickProps } from 'mol-geo/representation/structure/ball-and-stick'; +import { DistanceRestraintProps } from 'mol-geo/representation/structure/distance-restraint'; +import { BackboneProps } from 'mol-geo/representation/structure/backbone'; + +const spacefillProps: SpacefillProps = { + doubleSided: true, + colorTheme: { name: 'chain-id' }, + quality: 'auto', + useFog: false +} + +const ballAndStickProps: BallAndStickProps = { + doubleSided: true, + colorTheme: { name: 'chain-id' }, + sizeTheme: { name: 'uniform', value: 0.05 }, + linkRadius: 0.05, + quality: 'auto', + useFog: false +} + +const distanceRestraintProps: DistanceRestraintProps = { + doubleSided: true, + colorTheme: { name: 'chain-id' }, + linkRadius: 0.5, + quality: 'auto', + useFog: false +} + +const backboneProps: BackboneProps = { + doubleSided: true, + colorTheme: { name: 'chain-id' }, + quality: 'auto', + useFog: false +} export class FileLoader extends View<TransformListController, {}, { ctx: StateContext }> { render() { return <div className='molstar-file-loader'> <FileInput accept='*.cif' - onChange={files => { + onChange={async files => { if (files) { - const fileEntity = FileEntity.ofFile(this.props.ctx, files[0]) - MmcifFileToSpacefill.apply(this.props.ctx, fileEntity) + const ctx = this.props.ctx + const fileEntity = FileEntity.ofFile(ctx, files[0]) + const modelEntity = await MmcifFileToModel.apply(ctx, fileEntity) + const structureEntity = await ModelToStructure.apply(ctx, modelEntity) + + StructureToBallAndStick.apply(ctx, structureEntity, { ...ballAndStickProps, visible: true }) + StructureToSpacefill.apply(ctx, structureEntity, { ...spacefillProps, visible: false }) + StructureToDistanceRestraint.apply(ctx, structureEntity, { ...distanceRestraintProps, visible: false }) + StructureToBackbone.apply(ctx, structureEntity, { ...backboneProps, visible: true }) } }} /> diff --git a/src/mol-app/ui/transform/list.tsx b/src/mol-app/ui/transform/list.tsx index 57d5fcaa7cb7823719dfde819eb37df9c432e7c8..349dcffceea6e847407c73d0738c1ad6f80e2593 100644 --- a/src/mol-app/ui/transform/list.tsx +++ b/src/mol-app/ui/transform/list.tsx @@ -19,6 +19,9 @@ import { AnyEntity } from 'mol-view/state/entity'; import { FileLoader } from './file-loader'; import { ModelToStructure } from './model'; import { StructureCenter } from './structure'; +import { Cartoon } from './cartoon'; +import { DistanceRestraint } from './distance-restraint'; +import { Backbone } from './backbone'; function getTransformComponent(controller: TransformListController, entity: AnyEntity, transform: AnyTransform) { switch (transform.kind) { @@ -32,6 +35,12 @@ function getTransformComponent(controller: TransformListController, entity: AnyE return <Spacefill controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></Spacefill> case 'ballandstick-update': return <BallAndStick controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></BallAndStick> + case 'distancerestraint-update': + return <DistanceRestraint controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></DistanceRestraint> + case 'backbone-update': + return <Backbone controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></Backbone> + case 'cartoon-update': + return <Cartoon controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></Cartoon> } return <Transform controller={controller} entity={entity} transform={transform}></Transform> } diff --git a/src/mol-app/ui/visualization/image-canvas.tsx b/src/mol-app/ui/visualization/image-canvas.tsx index e6e329d0da3315cc844ae101a6ed4bc87c376042..f70699b2e5cce4f3839112c9bd3c9593724cf541 100644 --- a/src/mol-app/ui/visualization/image-canvas.tsx +++ b/src/mol-app/ui/visualization/image-canvas.tsx @@ -22,6 +22,12 @@ export class ImageCanvas extends React.Component<{ imageData: ImageData, aspectR private canvas: HTMLCanvasElement | null = null; private ctx: CanvasRenderingContext2D | null = null; + state = { + imageData: new ImageData(1, 1), + width: 1, + height: 1 + } + updateStateFromProps() { this.setState({ imageData: this.props.imageData, diff --git a/src/mol-data/db/table.ts b/src/mol-data/db/table.ts index 1237cba5a7f272778ad5c31fa5c993c8204c1683..15fd6aef229002f97e13fde5f609131cb19fa245 100644 --- a/src/mol-data/db/table.ts +++ b/src/mol-data/db/table.ts @@ -105,7 +105,7 @@ namespace Table { if (start === 0 && end === table._rowCount) return table; const ret = Object.create(null); const columns = Object.keys(schema); - ret._rowCount = view.length; + ret._rowCount = end - start; ret._columns = columns; ret._schema = schema; for (const k of columns) { diff --git a/src/mol-geo/representation/index.ts b/src/mol-geo/representation/index.ts index e7934d90d43f4024b5c5e238c520673db143b27c..ab18c4cdf0447be8479a86768b6cdaa2a4dfff39 100644 --- a/src/mol-geo/representation/index.ts +++ b/src/mol-geo/representation/index.ts @@ -23,7 +23,7 @@ export interface Representation<D, P extends RepresentationProps = {}> { } export interface Visual<D, P extends RepresentationProps = {}> { - readonly renderObjects: ReadonlyArray<RenderObject> + readonly renderObject: RenderObject create: (ctx: RuntimeContext, data: D, props: P) => Promise<void> update: (ctx: RuntimeContext, props: P) => Promise<boolean> getLoci: (pickingId: PickingId) => Loci diff --git a/src/mol-geo/representation/structure/backbone.ts b/src/mol-geo/representation/structure/backbone.ts new file mode 100644 index 0000000000000000000000000000000000000000..9fec379019ec0b853306079119f5d1a32c4bd96b --- /dev/null +++ b/src/mol-geo/representation/structure/backbone.ts @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { StructureRepresentation, StructureUnitsRepresentation } from '.'; +import { PickingId } from '../../util/picking'; +import { Structure } from 'mol-model/structure'; +import { Task } from 'mol-task'; +import { Loci } from 'mol-model/loci'; +import { MarkerAction } from '../../util/marker-data'; +import { PolymerBackboneVisual, DefaultPolymerBackboneProps } from './visual/polymer-backbone-cylinder'; + +export const DefaultBackboneProps = { + ...DefaultPolymerBackboneProps +} +export type BackboneProps = Partial<typeof DefaultBackboneProps> + +export function BackboneRepresentation(): StructureRepresentation<BackboneProps> { + const traceRepr = StructureUnitsRepresentation(PolymerBackboneVisual) + + return { + get renderObjects() { + return [ ...traceRepr.renderObjects ] + }, + get props() { + return { ...traceRepr.props } + }, + create: (structure: Structure, props: BackboneProps = {} as BackboneProps) => { + const p = Object.assign({}, DefaultBackboneProps, props) + return Task.create('BackboneRepresentation', async ctx => { + await traceRepr.create(structure, p).runInContext(ctx) + }) + }, + update: (props: BackboneProps) => { + const p = Object.assign({}, props) + return Task.create('Updating BackboneRepresentation', async ctx => { + await traceRepr.update(p).runInContext(ctx) + }) + }, + getLoci: (pickingId: PickingId) => { + return traceRepr.getLoci(pickingId) + }, + mark: (loci: Loci, action: MarkerAction) => { + traceRepr.mark(loci, action) + }, + destroy() { + traceRepr.destroy() + } + } +} \ No newline at end of file diff --git a/src/mol-geo/representation/structure/cartoon.ts b/src/mol-geo/representation/structure/cartoon.ts new file mode 100644 index 0000000000000000000000000000000000000000..445b6f92c25728ccd5f6cd9a2ea705557e45e3d6 --- /dev/null +++ b/src/mol-geo/representation/structure/cartoon.ts @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { StructureRepresentation, StructureUnitsRepresentation } from '.'; +import { PickingId } from '../../util/picking'; +import { Structure } from 'mol-model/structure'; +import { Task } from 'mol-task'; +import { Loci } from 'mol-model/loci'; +import { MarkerAction } from '../../util/marker-data'; +import { PolymerTraceVisual, DefaultPolymerTraceProps } from './visual/polymer-trace-mesh'; + +export const DefaultCartoonProps = { + ...DefaultPolymerTraceProps +} +export type CartoonProps = Partial<typeof DefaultCartoonProps> + +export function CartoonRepresentation(): StructureRepresentation<CartoonProps> { + const traceRepr = StructureUnitsRepresentation(PolymerTraceVisual) + + return { + get renderObjects() { + return [ ...traceRepr.renderObjects ] + }, + get props() { + return { ...traceRepr.props } + }, + create: (structure: Structure, props: CartoonProps = {} as CartoonProps) => { + const p = Object.assign({}, DefaultCartoonProps, props) + return Task.create('CartoonRepresentation', async ctx => { + await traceRepr.create(structure, p).runInContext(ctx) + }) + }, + update: (props: CartoonProps) => { + const p = Object.assign({}, props) + return Task.create('Updating CartoonRepresentation', async ctx => { + await traceRepr.update(p).runInContext(ctx) + }) + }, + getLoci: (pickingId: PickingId) => { + return traceRepr.getLoci(pickingId) + }, + mark: (loci: Loci, action: MarkerAction) => { + traceRepr.mark(loci, action) + }, + destroy() { + traceRepr.destroy() + } + } +} \ No newline at end of file diff --git a/src/mol-geo/representation/structure/index.ts b/src/mol-geo/representation/structure/index.ts index a93b138f23a159c47d25be664b8237d4ad1c646c..bc6c343baadbcbfa74b75a8759fa680a9fedfb03 100644 --- a/src/mol-geo/representation/structure/index.ts +++ b/src/mol-geo/representation/structure/index.ts @@ -79,12 +79,8 @@ export function StructureRepresentation<P extends StructureProps>(visualCtor: () } return { - get renderObjects() { - return visual.renderObjects - }, - get props() { - return _props - }, + get renderObjects() { return [ visual.renderObject ] }, + get props() { return _props }, create, update, getLoci, @@ -185,7 +181,7 @@ export function StructureUnitsRepresentation<P extends StructureProps>(visualCto return { get renderObjects() { const renderObjects: RenderObject[] = [] - visuals.forEach(({ visual }) => renderObjects.push(...visual.renderObjects)) + visuals.forEach(({ visual }) => renderObjects.push(visual.renderObject)) return renderObjects }, get props() { diff --git a/src/mol-geo/representation/structure/visual/cross-link-restraint-cylinder.ts b/src/mol-geo/representation/structure/visual/cross-link-restraint-cylinder.ts index e03979728e51036fb8e3891395c81c49fe03a4d1..2ae4e2e4b729e18c8ce23c265e8f5a41c3db17b0 100644 --- a/src/mol-geo/representation/structure/visual/cross-link-restraint-cylinder.ts +++ b/src/mol-geo/representation/structure/visual/cross-link-restraint-cylinder.ts @@ -6,7 +6,7 @@ import { ValueCell } from 'mol-util/value-cell' -import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object' +import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object' import { Link, Structure } from 'mol-model/structure'; import { DefaultStructureProps, StructureVisual } from '../index'; import { RuntimeContext } from 'mol-task' @@ -57,18 +57,15 @@ export const DefaultCrossLinkRestraintProps = { export type CrossLinkRestraintProps = Partial<typeof DefaultCrossLinkRestraintProps> export function CrossLinkRestraintVisual(): StructureVisual<CrossLinkRestraintProps> { - const renderObjects: RenderObject[] = [] - let cylinders: MeshRenderObject + let renderObject: MeshRenderObject let currentProps: typeof DefaultCrossLinkRestraintProps let mesh: Mesh let currentStructure: Structure return { - renderObjects, + get renderObject () { return renderObject }, async create(ctx: RuntimeContext, structure: Structure, props: CrossLinkRestraintProps = {}) { currentProps = Object.assign({}, DefaultCrossLinkRestraintProps, props) - - renderObjects.length = 0 // clear currentStructure = structure const elementCount = structure.crossLinkRestraints.count @@ -76,13 +73,8 @@ export function CrossLinkRestraintVisual(): StructureVisual<CrossLinkRestraintPr mesh = await createCrossLinkRestraintCylinderMesh(ctx, structure, currentProps) - if (ctx.shouldUpdate) await ctx.update('Computing link transforms'); const transforms = createIdentityTransform() - - if (ctx.shouldUpdate) await ctx.update('Computing link colors'); const color = createUniformColor({ value: 0x119911 }) // TODO - - if (ctx.shouldUpdate) await ctx.update('Computing link marks'); const marker = createMarkers(instanceCount * elementCount) const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount } @@ -97,28 +89,26 @@ export function CrossLinkRestraintVisual(): StructureVisual<CrossLinkRestraintPr } const state = createRenderableState(currentProps) - cylinders = createMeshRenderObject(values, state) - console.log(values, instanceCount, elementCount) - renderObjects.push(cylinders) + renderObject = createMeshRenderObject(values, state) }, async update(ctx: RuntimeContext, props: CrossLinkRestraintProps) { const newProps = Object.assign({}, currentProps, props) - if (!cylinders) return false + if (!renderObject) return false // TODO create in-place if (currentProps.radialSegments !== newProps.radialSegments) return false - updateMeshValues(cylinders.values, newProps) - updateRenderableState(cylinders.state, newProps) + updateMeshValues(renderObject.values, newProps) + updateRenderableState(renderObject.state, newProps) return false }, getLoci(pickingId: PickingId) { - return getLinkLoci(pickingId, currentStructure, cylinders.id) + return getLinkLoci(pickingId, currentStructure, renderObject.id) }, mark(loci: Loci, action: MarkerAction) { - markLink(loci, action, currentStructure, cylinders.values) + markLink(loci, action, currentStructure, renderObject.values) }, destroy() { // TODO diff --git a/src/mol-geo/representation/structure/visual/element-point.ts b/src/mol-geo/representation/structure/visual/element-point.ts index c6bfaf84cc74cec0ff366ff0c7dd1fd7a63a1815..e317a0a62ec7600f8a9170a9be9e13526e5e83a9 100644 --- a/src/mol-geo/representation/structure/visual/element-point.ts +++ b/src/mol-geo/representation/structure/visual/element-point.ts @@ -6,21 +6,21 @@ */ import { ValueCell } from 'mol-util/value-cell' -import { createPointRenderObject, RenderObject, PointRenderObject } from 'mol-gl/render-object' -import { Unit, Element } from 'mol-model/structure'; +import { createPointRenderObject, PointRenderObject } from 'mol-gl/render-object' +import { Unit } from 'mol-model/structure'; import { RuntimeContext } from 'mol-task' import { fillSerial } from 'mol-gl/renderable/util'; import { UnitsVisual, DefaultStructureProps } from '../index'; import VertexMap from '../../../shape/vertex-map'; import { SizeTheme } from '../../../theme'; -import { markElement } from './util/element'; +import { markElement, getElementLoci } from './util/element'; import { createTransforms, createColors, createSizes } from './util/common'; import { deepEqual, defaults } from 'mol-util'; -import { SortedArray, OrderedSet } from 'mol-data/int'; +import { SortedArray } from 'mol-data/int'; import { RenderableState, PointValues } from 'mol-gl/renderable'; import { PickingId } from '../../../util/picking'; -import { Loci, EmptyLoci } from 'mol-model/loci'; +import { Loci } from 'mol-model/loci'; import { MarkerAction, createMarkers } from '../../../util/marker-data'; import { Vec3 } from 'mol-math/linear-algebra'; @@ -49,8 +49,7 @@ export function createPointVertices(unit: Unit) { } export default function PointVisual(): UnitsVisual<PointProps> { - const renderObjects: RenderObject[] = [] - let points: PointRenderObject + let renderObject: PointRenderObject let currentProps = DefaultPointProps let currentGroup: Unit.SymmetryGroup @@ -58,11 +57,9 @@ export default function PointVisual(): UnitsVisual<PointProps> { let _elements: SortedArray return { - renderObjects, + get renderObject () { return renderObject }, async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: PointProps = {}) { currentProps = Object.assign({}, DefaultPointProps, props) - - renderObjects.length = 0 // clear currentGroup = group _units = group.units @@ -79,19 +76,10 @@ export default function PointVisual(): UnitsVisual<PointProps> { fillSerial(new Uint32Array(elementCount + 1)) ) - if (ctx.shouldUpdate) await ctx.update('Computing point vertices'); const vertices = createPointVertices(_units[0]) - - if (ctx.shouldUpdate) await ctx.update('Computing point transforms'); const transforms = createTransforms(group) - - if (ctx.shouldUpdate) await ctx.update('Computing point colors'); const color = createColors(group, elementCount, colorTheme) - - if (ctx.shouldUpdate) await ctx.update('Computing point sizes'); const size = createSizes(group, vertexMap, sizeTheme) - - if (ctx.shouldUpdate) await ctx.update('Computing spacefill marks'); const marker = createMarkers(instanceCount * elementCount) const values: PointValues = { @@ -118,11 +106,10 @@ export default function PointVisual(): UnitsVisual<PointProps> { visible: defaults(props.visible, true) } - points = createPointRenderObject(values, state) - renderObjects.push(points) + renderObject = createPointRenderObject(values, state) }, async update(ctx: RuntimeContext, props: PointProps) { - if (!points || !_units || !_elements) return false + if (!renderObject || !_units || !_elements) return false const newProps = { ...currentProps, ...props } if (deepEqual(currentProps, newProps)) { @@ -130,21 +117,8 @@ export default function PointVisual(): UnitsVisual<PointProps> { return true } - // const elementCount = OrderedSet.size(_elementGroup.elements) - // const unitCount = _units.length - - // const vertexMap = VertexMap.create( - // elementCount, - // elementCount + 1, - // fillSerial(new Uint32Array(elementCount)), - // fillSerial(new Uint32Array(elementCount + 1)) - // ) - if (!deepEqual(currentProps.colorTheme, newProps.colorTheme)) { console.log('colorTheme changed', currentProps.colorTheme, newProps.colorTheme) - // if (ctx.shouldUpdate) await ctx.update('Computing point colors'); - // const color = createColors(_units, _elementGroup, vertexMap, newProps.colorTheme) - // ValueCell.update(points.props.color, color) } if (!deepEqual(currentProps.sizeTheme, newProps.sizeTheme)) { @@ -155,16 +129,10 @@ export default function PointVisual(): UnitsVisual<PointProps> { return false }, getLoci(pickingId: PickingId) { - const { objectId, instanceId, elementId } = pickingId - if (points.id === objectId) { - const unit = currentGroup.units[instanceId] - const indices = OrderedSet.ofSingleton(elementId as Element.Index) - return Element.Loci([{ unit, indices }]) - } - return EmptyLoci + return getElementLoci(renderObject.id, currentGroup, pickingId) }, mark(loci: Loci, action: MarkerAction) { - markElement(points.values.tMarker, currentGroup, loci, action) + markElement(renderObject.values.tMarker, currentGroup, loci, action) }, destroy() { // TODO diff --git a/src/mol-geo/representation/structure/visual/element-sphere.ts b/src/mol-geo/representation/structure/visual/element-sphere.ts index 279ceb67aa2d78c8ef32baac6582b37195ca5f89..55002bfc9597d1f3ccf470a27af65a658d55a643 100644 --- a/src/mol-geo/representation/structure/visual/element-sphere.ts +++ b/src/mol-geo/representation/structure/visual/element-sphere.ts @@ -7,20 +7,19 @@ import { ValueCell } from 'mol-util/value-cell' -import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object' -import { Unit, Element } from 'mol-model/structure'; +import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object' +import { Unit } from 'mol-model/structure'; import { DefaultStructureProps, UnitsVisual } from '../index'; import { RuntimeContext } from 'mol-task' -import { createTransforms, createColors } from '../visual/util/common'; -import { createElementSphereMesh, markElement, getElementRadius } from '../visual/util/element'; +import { createTransforms, createColors } from './util/common'; +import { createElementSphereMesh, markElement, getElementRadius, getElementLoci } from './util/element'; import { deepEqual } from 'mol-util'; import { MeshValues } from 'mol-gl/renderable'; import { getMeshData } from '../../../util/mesh-data'; import { Mesh } from '../../../shape/mesh'; import { PickingId } from '../../../util/picking'; -import { OrderedSet } from 'mol-data/int'; import { createMarkers, MarkerAction } from '../../../util/marker-data'; -import { Loci, EmptyLoci } from 'mol-model/loci'; +import { Loci } from 'mol-model/loci'; import { SizeTheme } from '../../../theme'; import { createMeshValues, updateMeshValues, updateRenderableState, createRenderableState, DefaultMeshProps } from '../../util'; @@ -34,18 +33,15 @@ export const DefaultElementSphereProps = { export type ElementSphereProps = Partial<typeof DefaultElementSphereProps> export function ElementSphereVisual(): UnitsVisual<ElementSphereProps> { - const renderObjects: RenderObject[] = [] - let spheres: MeshRenderObject + let renderObject: MeshRenderObject let currentProps: typeof DefaultElementSphereProps let mesh: Mesh let currentGroup: Unit.SymmetryGroup return { - renderObjects, + get renderObject () { return renderObject }, async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: ElementSphereProps = {}) { currentProps = Object.assign({}, DefaultElementSphereProps, props) - - renderObjects.length = 0 // clear currentGroup = group const { detail, colorTheme, sizeTheme, unitKinds } = { ...DefaultElementSphereProps, ...props } @@ -58,13 +54,8 @@ export function ElementSphereVisual(): UnitsVisual<ElementSphereProps> { ? await createElementSphereMesh(ctx, unit, radius, detail, mesh) : Mesh.createEmpty(mesh) - if (ctx.shouldUpdate) await ctx.update('Computing spacefill transforms'); const transforms = createTransforms(group) - - if (ctx.shouldUpdate) await ctx.update('Computing spacefill colors'); const color = createColors(group, elementCount, colorTheme) - - if (ctx.shouldUpdate) await ctx.update('Computing spacefill marks'); const marker = createMarkers(instanceCount * elementCount) const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount } @@ -79,13 +70,12 @@ export function ElementSphereVisual(): UnitsVisual<ElementSphereProps> { } const state = createRenderableState(currentProps) - spheres = createMeshRenderObject(values, state) - renderObjects.push(spheres) + renderObject = createMeshRenderObject(values, state) }, async update(ctx: RuntimeContext, props: ElementSphereProps) { const newProps = Object.assign({}, currentProps, props) - if (!spheres) return false + if (!renderObject) return false let updateColor = false @@ -93,7 +83,7 @@ export function ElementSphereVisual(): UnitsVisual<ElementSphereProps> { const unit = currentGroup.units[0] const radius = getElementRadius(unit, newProps.sizeTheme) mesh = await createElementSphereMesh(ctx, unit, radius, newProps.detail, mesh) - ValueCell.update(spheres.values.drawCount, mesh.triangleCount * 3) + ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3) updateColor = true } @@ -103,27 +93,21 @@ export function ElementSphereVisual(): UnitsVisual<ElementSphereProps> { if (updateColor) { const elementCount = currentGroup.elements.length - if (ctx.shouldUpdate) await ctx.update('Computing spacefill colors'); - createColors(currentGroup, elementCount, newProps.colorTheme, spheres.values) + if (ctx.shouldUpdate) await ctx.update('Computing sphere colors'); + createColors(currentGroup, elementCount, newProps.colorTheme, renderObject.values) } - updateMeshValues(spheres.values, newProps) - updateRenderableState(spheres.state, newProps) + updateMeshValues(renderObject.values, newProps) + updateRenderableState(renderObject.state, newProps) currentProps = newProps return true }, getLoci(pickingId: PickingId) { - const { objectId, instanceId, elementId } = pickingId - if (spheres.id === objectId) { - const unit = currentGroup.units[instanceId] - const indices = OrderedSet.ofSingleton(elementId as Element.Index); - return Element.Loci([{ unit, indices }]) - } - return EmptyLoci + return getElementLoci(renderObject.id, currentGroup, pickingId) }, mark(loci: Loci, action: MarkerAction) { - markElement(spheres.values.tMarker, currentGroup, loci, action) + markElement(renderObject.values.tMarker, currentGroup, loci, action) }, destroy() { // TODO 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 1195e0ab787fde13c298aab34fec8b93be76781b..96679b42f431bea5b642927bc99da78e84b74693 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 @@ -6,7 +6,7 @@ import { ValueCell } from 'mol-util/value-cell' -import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object' +import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object' import { Link, Structure } from 'mol-model/structure'; import { DefaultStructureProps, StructureVisual } from '../index'; import { RuntimeContext } from 'mol-task' @@ -54,18 +54,15 @@ export const DefaultInterUnitLinkProps = { export type InterUnitLinkProps = Partial<typeof DefaultInterUnitLinkProps> export function InterUnitLinkVisual(): StructureVisual<InterUnitLinkProps> { - const renderObjects: RenderObject[] = [] - let cylinders: MeshRenderObject + let renderObject: MeshRenderObject let currentProps: typeof DefaultInterUnitLinkProps let mesh: Mesh let currentStructure: Structure return { - renderObjects, + get renderObject () { return renderObject }, async create(ctx: RuntimeContext, structure: Structure, props: InterUnitLinkProps = {}) { currentProps = Object.assign({}, DefaultInterUnitLinkProps, props) - - renderObjects.length = 0 // clear currentStructure = structure const elementCount = structure.links.bondCount @@ -73,13 +70,8 @@ export function InterUnitLinkVisual(): StructureVisual<InterUnitLinkProps> { mesh = await createInterUnitLinkCylinderMesh(ctx, structure, currentProps) - if (ctx.shouldUpdate) await ctx.update('Computing link transforms'); const transforms = createIdentityTransform() - - if (ctx.shouldUpdate) await ctx.update('Computing link colors'); const color = createUniformColor({ value: 0x999911 }) // TODO - - if (ctx.shouldUpdate) await ctx.update('Computing link marks'); const marker = createMarkers(instanceCount * elementCount) const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount } @@ -94,27 +86,26 @@ export function InterUnitLinkVisual(): StructureVisual<InterUnitLinkProps> { } const state = createRenderableState(currentProps) - cylinders = createMeshRenderObject(values, state) - renderObjects.push(cylinders) + renderObject = createMeshRenderObject(values, state) }, async update(ctx: RuntimeContext, props: InterUnitLinkProps) { const newProps = Object.assign({}, currentProps, props) - if (!cylinders) return false + if (!renderObject) return false // TODO create in-place if (currentProps.radialSegments !== newProps.radialSegments) return false - updateMeshValues(cylinders.values, newProps) - updateRenderableState(cylinders.state, newProps) + updateMeshValues(renderObject.values, newProps) + updateRenderableState(renderObject.state, newProps) return false }, getLoci(pickingId: PickingId) { - return getLinkLoci(pickingId, currentStructure, cylinders.id) + return getLinkLoci(pickingId, currentStructure, renderObject.id) }, mark(loci: Loci, action: MarkerAction) { - markLink(loci, action, currentStructure, cylinders.values) + markLink(loci, action, currentStructure, renderObject.values) }, destroy() { // TODO 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 c747d572699659886f9fa2c05c128eb75cef75a1..2a9af6401e2aafa4896352fe2c872cda4c24607c 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 @@ -7,7 +7,7 @@ import { ValueCell } from 'mol-util/value-cell' -import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object' +import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object' import { Unit, Link } from 'mol-model/structure'; import { UnitsVisual, DefaultStructureProps } from '../index'; import { RuntimeContext } from 'mol-task' @@ -70,18 +70,15 @@ export const DefaultIntraUnitLinkProps = { export type IntraUnitLinkProps = Partial<typeof DefaultIntraUnitLinkProps> export function IntraUnitLinkVisual(): UnitsVisual<IntraUnitLinkProps> { - const renderObjects: RenderObject[] = [] - let cylinders: MeshRenderObject + let renderObject: MeshRenderObject let currentProps: typeof DefaultIntraUnitLinkProps let mesh: Mesh let currentGroup: Unit.SymmetryGroup return { - renderObjects, + get renderObject () { return renderObject }, async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: IntraUnitLinkProps = {}) { currentProps = Object.assign({}, DefaultIntraUnitLinkProps, props) - - renderObjects.length = 0 // clear currentGroup = group const unit = group.units[0] @@ -90,13 +87,8 @@ export function IntraUnitLinkVisual(): UnitsVisual<IntraUnitLinkProps> { mesh = await createIntraUnitLinkCylinderMesh(ctx, unit, currentProps) - if (ctx.shouldUpdate) await ctx.update('Computing link transforms'); const transforms = createTransforms(group) - - if (ctx.shouldUpdate) await ctx.update('Computing link colors'); const color = chainIdLinkColorData({ group, elementCount }) // TODO - - if (ctx.shouldUpdate) await ctx.update('Computing link marks'); const marker = createMarkers(instanceCount * elementCount) const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount } @@ -111,27 +103,26 @@ export function IntraUnitLinkVisual(): UnitsVisual<IntraUnitLinkProps> { } const state = createRenderableState(currentProps) - cylinders = createMeshRenderObject(values, state) - renderObjects.push(cylinders) + renderObject = createMeshRenderObject(values, state) }, async update(ctx: RuntimeContext, props: IntraUnitLinkProps) { const newProps = Object.assign({}, currentProps, props) - if (!cylinders) return false + if (!renderObject) return false // TODO create in-place if (currentProps.radialSegments !== newProps.radialSegments) return false - updateMeshValues(cylinders.values, newProps) - updateRenderableState(cylinders.state, newProps) + updateMeshValues(renderObject.values, newProps) + updateRenderableState(renderObject.state, newProps) return true }, getLoci(pickingId: PickingId) { - return getLinkLoci(pickingId, currentGroup, cylinders.id) + return getLinkLoci(pickingId, currentGroup, renderObject.id) }, mark(loci: Loci, action: MarkerAction) { - markLink(loci, action, currentGroup, cylinders.values) + markLink(loci, action, currentGroup, renderObject.values) }, destroy() { // TODO diff --git a/src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts b/src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts new file mode 100644 index 0000000000000000000000000000000000000000..4ab1548d5fdda9a642b40eefc8d23c49e9d2693d --- /dev/null +++ b/src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts @@ -0,0 +1,144 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { ValueCell } from 'mol-util/value-cell' + +import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object' +import { Unit } from 'mol-model/structure'; +import { DefaultStructureProps, UnitsVisual } from '../index'; +import { RuntimeContext } from 'mol-task' +import { createTransforms, createColors } from './util/common'; +import { deepEqual } from 'mol-util'; +import { MeshValues } from 'mol-gl/renderable'; +import { getMeshData } from '../../../util/mesh-data'; +import { Mesh } from '../../../shape/mesh'; +import { PickingId } from '../../../util/picking'; +import { createMarkers, MarkerAction } from '../../../util/marker-data'; +import { Loci } from 'mol-model/loci'; +import { SizeTheme } from '../../../theme'; +import { createMeshValues, updateMeshValues, updateRenderableState, createRenderableState, DefaultMeshProps } from '../../util'; +import { MeshBuilder } from '../../../shape/mesh-builder'; +import { getPolymerElementCount, PolymerBackboneIterator } from './util/polymer'; +import { getElementLoci, markElement } from './util/element'; + +async function createPolymerBackboneCylinderMesh(ctx: RuntimeContext, unit: Unit, mesh?: Mesh) { + const polymerElementCount = getPolymerElementCount(unit) + console.log('polymerElementCount backbone', polymerElementCount) + if (!polymerElementCount) return Mesh.createEmpty(mesh) + + // TODO better vertex count estimates + const builder = MeshBuilder.create(polymerElementCount * 30, polymerElementCount * 30 / 2, mesh) + + let i = 0 + const polymerTraceIt = PolymerBackboneIterator(unit) + while (polymerTraceIt.hasNext) { + const { indexA, indexB, posA, posB } = polymerTraceIt.move() + builder.setId(indexA) + // TODO size theme + builder.addCylinder(posA, posB, 0.5, { radiusTop: 0.2, radiusBottom: 0.2 }) + builder.setId(indexB) + builder.addCylinder(posB, posA, 0.5, { radiusTop: 0.2, radiusBottom: 0.2 }) + + if (i % 10000 === 0 && ctx.shouldUpdate) { + await ctx.update({ message: 'Backbone mesh', current: i, max: polymerElementCount }); + } + ++i + } + + return builder.getMesh() +} + +export const DefaultPolymerBackboneProps = { + ...DefaultMeshProps, + ...DefaultStructureProps, + sizeTheme: { name: 'physical', factor: 1 } as SizeTheme, + detail: 0, + unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[] +} +export type PolymerBackboneProps = Partial<typeof DefaultPolymerBackboneProps> + +export function PolymerBackboneVisual(): UnitsVisual<PolymerBackboneProps> { + let renderObject: MeshRenderObject + let currentProps: typeof DefaultPolymerBackboneProps + let mesh: Mesh + let currentGroup: Unit.SymmetryGroup + + return { + get renderObject () { return renderObject }, + async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: PolymerBackboneProps = {}) { + currentProps = Object.assign({}, DefaultPolymerBackboneProps, props) + currentGroup = group + + const { colorTheme, unitKinds } = { ...DefaultPolymerBackboneProps, ...props } + const instanceCount = group.units.length + const elementCount = group.elements.length + const unit = group.units[0] + + mesh = unitKinds.includes(unit.kind) + ? await createPolymerBackboneCylinderMesh(ctx, unit, mesh) + : Mesh.createEmpty(mesh) + console.log(mesh) + + const transforms = createTransforms(group) + const color = createColors(group, elementCount, colorTheme) + const marker = createMarkers(instanceCount * elementCount) + + const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount } + + const values: MeshValues = { + ...getMeshData(mesh), + ...color, + ...marker, + aTransform: transforms, + elements: mesh.indexBuffer, + ...createMeshValues(currentProps, counts), + aColor: ValueCell.create(new Float32Array(mesh.vertexCount * 3)) + } + const state = createRenderableState(currentProps) + + renderObject = createMeshRenderObject(values, state) + }, + async update(ctx: RuntimeContext, props: PolymerBackboneProps) { + const newProps = Object.assign({}, currentProps, props) + + if (!renderObject) return false + + let updateColor = false + + if (newProps.detail !== currentProps.detail) { + const unit = currentGroup.units[0] + mesh = await createPolymerBackboneCylinderMesh(ctx, unit, mesh) + ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3) + updateColor = true + } + + if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) { + updateColor = true + } + + if (updateColor) { + const elementCount = currentGroup.elements.length + if (ctx.shouldUpdate) await ctx.update('Computing trace colors'); + createColors(currentGroup, elementCount, newProps.colorTheme, renderObject.values) + } + + updateMeshValues(renderObject.values, newProps) + updateRenderableState(renderObject.state, newProps) + + currentProps = newProps + return true + }, + getLoci(pickingId: PickingId) { + return getElementLoci(renderObject.id, currentGroup, pickingId) + }, + mark(loci: Loci, action: MarkerAction) { + markElement(renderObject.values.tMarker, currentGroup, loci, action) + }, + destroy() { + // TODO + } + } +} diff --git a/src/mol-geo/representation/structure/visual/polymer-backbone-sphere.ts b/src/mol-geo/representation/structure/visual/polymer-backbone-sphere.ts new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts b/src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts new file mode 100644 index 0000000000000000000000000000000000000000..75cccb7ab0beb27269f970193c61f32af954975a --- /dev/null +++ b/src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts @@ -0,0 +1,149 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { ValueCell } from 'mol-util/value-cell' + +import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object' +import { Unit, Element } from 'mol-model/structure'; +import { DefaultStructureProps, UnitsVisual } from '../index'; +import { RuntimeContext } from 'mol-task' +import { createTransforms, createColors } from './util/common'; +import { markElement } from './util/element'; +import { deepEqual } from 'mol-util'; +import { MeshValues } from 'mol-gl/renderable'; +import { getMeshData } from '../../../util/mesh-data'; +import { Mesh } from '../../../shape/mesh'; +import { PickingId } from '../../../util/picking'; +import { OrderedSet } from 'mol-data/int'; +import { createMarkers, MarkerAction } from '../../../util/marker-data'; +import { Loci, EmptyLoci } from 'mol-model/loci'; +import { SizeTheme } from '../../../theme'; +import { createMeshValues, updateMeshValues, updateRenderableState, createRenderableState, DefaultMeshProps } from '../../util'; +import { MeshBuilder } from '../../../shape/mesh-builder'; +import { getPolymerElementCount, PolymerBackboneIterator } from './util/polymer'; + +async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, mesh?: Mesh) { + const polymerElementCount = getPolymerElementCount(unit) + console.log('polymerElementCount', polymerElementCount) + if (!polymerElementCount) return Mesh.createEmpty(mesh) + + // TODO better vertex count estimates + const builder = MeshBuilder.create(polymerElementCount * 30, polymerElementCount * 30 / 2, mesh) + + let i = 0 + const polymerTraceIt = PolymerBackboneIterator(unit) + while (polymerTraceIt.hasNext) { + const v = polymerTraceIt.move() + builder.setId(v.indexA) + // TODO size theme + builder.addCylinder(v.posA, v.posB, 0.5, { radiusTop: 0.2, radiusBottom: 0.2 }) + builder.setId(v.indexB) + builder.addCylinder(v.posB, v.posA, 0.5, { radiusTop: 0.2, radiusBottom: 0.2 }) + + if (i % 10000 === 0 && ctx.shouldUpdate) { + await ctx.update({ message: 'Backbone mesh', current: i, max: polymerElementCount }); + } + ++i + } + + return builder.getMesh() +} + +export const DefaultPolymerTraceProps = { + ...DefaultMeshProps, + ...DefaultStructureProps, + sizeTheme: { name: 'physical', factor: 1 } as SizeTheme, + detail: 0, + unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[] +} +export type PolymerTraceProps = Partial<typeof DefaultPolymerTraceProps> + +export function PolymerTraceVisual(): UnitsVisual<PolymerTraceProps> { + let renderObject: MeshRenderObject + let currentProps: typeof DefaultPolymerTraceProps + let mesh: Mesh + let currentGroup: Unit.SymmetryGroup + + return { + get renderObject () { return renderObject }, + async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: PolymerTraceProps = {}) { + currentProps = Object.assign({}, DefaultPolymerTraceProps, props) + currentGroup = group + + const { colorTheme, unitKinds } = { ...DefaultPolymerTraceProps, ...props } + const instanceCount = group.units.length + const elementCount = group.elements.length + const unit = group.units[0] + + mesh = unitKinds.includes(unit.kind) + ? await createPolymerTraceMesh(ctx, unit, mesh) + : Mesh.createEmpty(mesh) + + const transforms = createTransforms(group) + const color = createColors(group, elementCount, colorTheme) + const marker = createMarkers(instanceCount * elementCount) + + const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount } + + const values: MeshValues = { + ...getMeshData(mesh), + ...color, + ...marker, + aTransform: transforms, + elements: mesh.indexBuffer, + ...createMeshValues(currentProps, counts), + } + const state = createRenderableState(currentProps) + + renderObject = createMeshRenderObject(values, state) + }, + async update(ctx: RuntimeContext, props: PolymerTraceProps) { + const newProps = Object.assign({}, currentProps, props) + + if (!renderObject) return false + + let updateColor = false + + if (newProps.detail !== currentProps.detail) { + const unit = currentGroup.units[0] + mesh = await createPolymerTraceMesh(ctx, unit, mesh) + ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3) + updateColor = true + } + + if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) { + updateColor = true + } + + if (updateColor) { + const elementCount = currentGroup.elements.length + if (ctx.shouldUpdate) await ctx.update('Computing trace colors'); + createColors(currentGroup, elementCount, newProps.colorTheme, renderObject.values) + } + + updateMeshValues(renderObject.values, newProps) + updateRenderableState(renderObject.state, newProps) + + currentProps = newProps + return true + }, + getLoci(pickingId: PickingId) { + const { objectId, instanceId, elementId } = pickingId + if (renderObject.id === objectId) { + const unit = currentGroup.units[instanceId] + const indices = OrderedSet.ofSingleton(elementId as Element.Index); + return Element.Loci([{ unit, indices }]) + } + return EmptyLoci + }, + mark(loci: Loci, action: MarkerAction) { + markElement(renderObject.values.tMarker, currentGroup, loci, action) + }, + destroy() { + // TODO + } + } +} diff --git a/src/mol-geo/representation/structure/visual/util/common.ts b/src/mol-geo/representation/structure/visual/util/common.ts index eee96d8bba4364afbe38ec9bbc6a3113a3dfc212..4b8c39719436836067f0a97b2c7d63dd918cc9d1 100644 --- a/src/mol-geo/representation/structure/visual/util/common.ts +++ b/src/mol-geo/representation/structure/visual/util/common.ts @@ -1,5 +1,3 @@ - - /** * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. * @@ -68,4 +66,4 @@ export function createSizes(group: Unit.SymmetryGroup, vertexMap: VertexMap, pro case 'physical': return physicalSizeData(defaults(props.factor, 1), { group, vertexMap }) } -} +} \ No newline at end of file diff --git a/src/mol-geo/representation/structure/visual/util/element.ts b/src/mol-geo/representation/structure/visual/util/element.ts index 396b24996ac023859f221a7678d016238d388658..9e498df7dfa40ae272894d786dbe86e692467687 100644 --- a/src/mol-geo/representation/structure/visual/util/element.ts +++ b/src/mol-geo/representation/structure/visual/util/element.ts @@ -13,10 +13,11 @@ import { Mesh } from '../../../../shape/mesh'; import { MeshBuilder } from '../../../../shape/mesh-builder'; import { ValueCell, defaults } from 'mol-util'; import { TextureImage } from 'mol-gl/renderable/util'; -import { Loci, isEveryLoci } from 'mol-model/loci'; +import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci'; import { MarkerAction, applyMarkerAction } from '../../../../util/marker-data'; -import { Interval } from 'mol-data/int'; +import { Interval, OrderedSet } from 'mol-data/int'; import { getPhysicalRadius } from '../../../../theme/structure/size/physical'; +import { PickingId } from '../../../../util/picking'; export function getElementRadius(unit: Unit, props: SizeTheme): Element.Property<number> { switch (props.name) { @@ -55,7 +56,6 @@ export async function createElementSphereMesh(ctx: RuntimeContext, unit: Unit, r return meshBuilder.getMesh() } - export function markElement(tMarker: ValueCell<TextureImage>, group: Unit.SymmetryGroup, loci: Loci, action: MarkerAction) { let changed = false const elementCount = group.elements.length @@ -91,3 +91,13 @@ export function markElement(tMarker: ValueCell<TextureImage>, group: Unit.Symmet ValueCell.update(tMarker, tMarker.ref.value) } } + +export function getElementLoci(id: number, group: Unit.SymmetryGroup, pickingId: PickingId) { + const { objectId, instanceId, elementId } = pickingId + if (id === objectId) { + const unit = group.units[instanceId] + const indices = OrderedSet.ofSingleton(elementId as Element.Index); + return Element.Loci([{ unit, indices }]) + } + return EmptyLoci +} \ No newline at end of file diff --git a/src/mol-geo/representation/structure/visual/util/polymer.ts b/src/mol-geo/representation/structure/visual/util/polymer.ts new file mode 100644 index 0000000000000000000000000000000000000000..b663fc5a08ffbbeeb0e0ba988ed94b9cc7f51bb0 --- /dev/null +++ b/src/mol-geo/representation/structure/visual/util/polymer.ts @@ -0,0 +1,343 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Unit, Element, StructureProperties } from 'mol-model/structure'; +import { Segmentation } from 'mol-data/int'; +import { MoleculeType } from 'mol-model/structure/model/types'; +import Iterator from 'mol-data/iterator'; +import { SegmentIterator } from 'mol-data/int/impl/segmentation'; +import { Vec3 } from 'mol-math/linear-algebra'; +import { SymmetryOperator } from 'mol-math/geometry'; + +export function getPolymerElementCount(unit: Unit) { + let count = 0 + const { elements } = unit + const l = Element.Location(unit) + if (Unit.isAtomic(unit)) { + const { polymerSegments, residueSegments } = unit.model.atomicHierarchy + const polymerIt = Segmentation.transientSegments(polymerSegments, elements); + const residuesIt = Segmentation.transientSegments(residueSegments, elements); + while (polymerIt.hasNext) { + residuesIt.setSegment(polymerIt.move()); + while (residuesIt.hasNext) { + residuesIt.move(); + count++ + } + } + } else if (Unit.isSpheres(unit)) { + for (let i = 0, il = elements.length; i < il; ++i) { + l.element = elements[i] + if (StructureProperties.entity.type(l) === 'polymer') count++ + } + } + return count +} + +function getTraceName(l: Element.Location) { + const compId = StructureProperties.residue.label_comp_id(l) + const chemCompMap = l.unit.model.properties.chemicalComponentMap + const cc = chemCompMap.get(compId) + const moleculeType = cc ? cc.moleculeType : MoleculeType.unknown + let traceName = '' + if (moleculeType === MoleculeType.protein) { + traceName = 'CA' + } else if (moleculeType === MoleculeType.DNA || moleculeType === MoleculeType.RNA) { + traceName = 'P' + } + return traceName +} + +function setTraceElement(l: Element.Location, residueSegment: Segmentation.Segment<Element>) { + const elements = l.unit.elements + l.element = elements[residueSegment.start] + const traceName = getTraceName(l) + + for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) { + l.element = elements[j]; + if (StructureProperties.atom.label_atom_id(l) === traceName) return j + } + return residueSegment.end - 1 +} + +/** Iterates over consecutive pairs of residues/coarse elements in polymers */ +export function PolymerBackboneIterator(unit: Unit): Iterator<PolymerBackbonePair> { + switch (unit.kind) { + case Unit.Kind.Atomic: return new AtomicPolymerBackboneIterator(unit) + case Unit.Kind.Spheres: + case Unit.Kind.Gaussians: + return new CoarsePolymerBackboneIterator(unit) + } +} + +interface PolymerBackbonePair { + centerA: Element.Location + centerB: Element.Location + indexA: number + indexB: number + posA: Vec3 + posB: Vec3 +} + +function createPolymerBackbonePair (unit: Unit) { + return { + centerA: Element.Location(unit), + centerB: Element.Location(unit), + indexA: 0, + indexB: 0, + posA: Vec3.zero(), + posB: Vec3.zero() + } +} + +const enum AtomicPolymerBackboneIteratorState { nextPolymer, firstResidue, nextResidue } + +export class AtomicPolymerBackboneIterator<T extends number = number> implements Iterator<PolymerBackbonePair> { + private value: PolymerBackbonePair + + private polymerIt: SegmentIterator<Element> + private residueIt: SegmentIterator<Element> + private polymerSegment: Segmentation.Segment<Element> + private state: AtomicPolymerBackboneIteratorState = AtomicPolymerBackboneIteratorState.nextPolymer + private pos: SymmetryOperator.CoordinateMapper + + hasNext: boolean = false; + + move() { + const { residueIt, polymerIt, value, pos } = this + + if (this.state === AtomicPolymerBackboneIteratorState.nextPolymer) { + if (polymerIt.hasNext) { + this.polymerSegment = polymerIt.move(); + residueIt.setSegment(this.polymerSegment); + this.state = AtomicPolymerBackboneIteratorState.firstResidue + } + } + + if (this.state === AtomicPolymerBackboneIteratorState.firstResidue) { + const residueSegment = residueIt.move(); + if (residueIt.hasNext) { + value.indexB = setTraceElement(value.centerB, residueSegment) + pos(value.centerB.element, value.posB) + this.state = AtomicPolymerBackboneIteratorState.nextResidue + } else { + this.state = AtomicPolymerBackboneIteratorState.nextPolymer + } + + } + + if (this.state === AtomicPolymerBackboneIteratorState.nextResidue) { + const residueSegment = residueIt.move(); + value.centerA.element = value.centerB.element + value.indexA = value.indexB + Vec3.copy(value.posA, value.posB) + value.indexB = setTraceElement(value.centerB, residueSegment) + pos(value.centerB.element, value.posB) + + if (!residueIt.hasNext) { + this.state = AtomicPolymerBackboneIteratorState.nextPolymer + } + } + + this.hasNext = residueIt.hasNext || polymerIt.hasNext + + return this.value; + } + + constructor(unit: Unit.Atomic) { + const { polymerSegments, residueSegments } = unit.model.atomicHierarchy + this.polymerIt = Segmentation.transientSegments(polymerSegments, unit.elements); + this.residueIt = Segmentation.transientSegments(residueSegments, unit.elements); + this.pos = unit.conformation.invariantPosition + this.value = createPolymerBackbonePair(unit) + this.hasNext = this.residueIt.hasNext || this.polymerIt.hasNext + } +} + +const enum CoarsePolymerBackboneIteratorState { nextPolymer, firstElement, nextElement } + +export class CoarsePolymerBackboneIterator<T extends number = number> implements Iterator<PolymerBackbonePair> { + private value: PolymerBackbonePair + + private polymerIt: SegmentIterator<Element> + private polymerSegment: Segmentation.Segment<Element> + private state: CoarsePolymerBackboneIteratorState = CoarsePolymerBackboneIteratorState.nextPolymer + private pos: SymmetryOperator.CoordinateMapper + private elementIndex: number + + hasNext: boolean = false; + + move() { + const { polymerIt, value, pos } = this + + if (this.state === CoarsePolymerBackboneIteratorState.nextPolymer) { + if (polymerIt.hasNext) { + this.polymerSegment = polymerIt.move(); + this.elementIndex = this.polymerSegment.start + this.state = CoarsePolymerBackboneIteratorState.firstElement + } + } + + if (this.state === CoarsePolymerBackboneIteratorState.firstElement) { + this.elementIndex += 1 + if (this.elementIndex + 1 < this.polymerSegment.end) { + value.centerB.element = value.centerB.unit.elements[this.elementIndex] + value.indexB = this.elementIndex + pos(value.centerB.element, value.posB) + + this.state = CoarsePolymerBackboneIteratorState.nextElement + } else { + this.state = CoarsePolymerBackboneIteratorState.nextPolymer + } + + } + + if (this.state === CoarsePolymerBackboneIteratorState.nextElement) { + this.elementIndex += 1 + value.centerA.element = value.centerB.element + value.indexA = value.indexB + Vec3.copy(value.posA, value.posB) + + value.centerB.element = value.centerB.unit.elements[this.elementIndex] + value.indexB = this.elementIndex + pos(value.centerB.element, value.posB) + + if (this.elementIndex + 1 >= this.polymerSegment.end) { + this.state = CoarsePolymerBackboneIteratorState.nextPolymer + } + } + + this.hasNext = this.elementIndex + 1 < this.polymerSegment.end || polymerIt.hasNext + + return this.value; + } + + constructor(unit: Unit.Spheres | Unit.Gaussians) { + const { polymerSegments } = Unit.isSpheres(unit) + ? unit.model.coarseHierarchy.spheres + : unit.model.coarseHierarchy.gaussians + this.polymerIt = Segmentation.transientSegments(polymerSegments, unit.elements); + + this.pos = unit.conformation.invariantPosition + this.value = createPolymerBackbonePair(unit) + + this.hasNext = this.polymerIt.hasNext + } +} + + + + +/** + * Iterates over individual residues/coarse elements in polymers while providing information + * about the neighbourhood in the underlying model for drawing splines + */ +export function PolymerTraceIterator(unit: Unit): Iterator<PolymerTraceElement> { + switch (unit.kind) { + case Unit.Kind.Atomic: return new AtomicPolymerTraceIterator(unit) + case Unit.Kind.Spheres: + case Unit.Kind.Gaussians: + return new CoarsePolymerTraceIterator(unit) + } +} + +interface PolymerTraceElement { + center: Element.Location + index: number + pos: Vec3 + posPrev: Vec3 + posNext: Vec3 + posNextNext: Vec3 +} + +function createPolymerTraceElement (unit: Unit) { + return { + center: Element.Location(unit), + index: 0, + pos: Vec3.zero(), + posPrev: Vec3.zero(), + posNext: Vec3.zero(), + posNextNext: Vec3.zero() + } +} + +// const enum AtomicPolymerTraceIteratorState { nextPolymer, firstResidue, nextResidue } + +export class AtomicPolymerTraceIterator<T extends number = number> implements Iterator<PolymerTraceElement> { + private value: PolymerTraceElement + + private polymerIt: SegmentIterator<Element> + private residueIt: SegmentIterator<Element> + // private polymerSegment: Segmentation.Segment<Element> + // private state: AtomicPolymerTraceIteratorState = AtomicPolymerTraceIteratorState.nextPolymer + // private pos: SymmetryOperator.CoordinateMapper + + hasNext: boolean = false; + + move() { + // const { residueIt, polymerIt, value, pos } = this + + // if (this.state === AtomicPolymerTraceIteratorState.nextPolymer) { + // if (polymerIt.hasNext) { + // this.polymerSegment = polymerIt.move(); + // residueIt.setSegment(this.polymerSegment); + // this.state = AtomicPolymerTraceIteratorState.firstResidue + // } + // } + + // if (this.state === AtomicPolymerTraceIteratorState.firstResidue) { + // const residueSegment = residueIt.move(); + // if (residueIt.hasNext) { + // value.indexB = setTraceElement(value.centerB, residueSegment) + // pos(value.centerB.element, value.posB) + // this.state = AtomicPolymerTraceIteratorState.nextResidue + // } else { + // this.state = AtomicPolymerTraceIteratorState.nextPolymer + // } + + // } + + // if (this.state === AtomicPolymerTraceIteratorState.nextResidue) { + // const residueSegment = residueIt.move(); + // value.centerA.element = value.centerB.element + // value.indexA = value.indexB + // Vec3.copy(value.posA, value.posB) + // value.indexB = setTraceElement(value.centerB, residueSegment) + // pos(value.centerB.element, value.posB) + + // if (!residueIt.hasNext) { + // this.state = AtomicPolymerTraceIteratorState.nextPolymer + // } + // } + + // this.hasNext = residueIt.hasNext || polymerIt.hasNext + + return this.value; + } + + constructor(unit: Unit.Atomic) { + const { polymerSegments, residueSegments } = unit.model.atomicHierarchy + this.polymerIt = Segmentation.transientSegments(polymerSegments, unit.elements); + this.residueIt = Segmentation.transientSegments(residueSegments, unit.elements); + // this.pos = unit.conformation.invariantPosition + this.value = createPolymerTraceElement(unit) + this.hasNext = this.residueIt.hasNext || this.polymerIt.hasNext + } +} + +export class CoarsePolymerTraceIterator<T extends number = number> implements Iterator<PolymerTraceElement> { + private value: PolymerTraceElement + + hasNext: boolean = false; + + move() { + return this.value; + } + + constructor(unit: Unit.Spheres | Unit.Gaussians) { + this.value = createPolymerTraceElement(unit) + this.hasNext = false + } +} \ No newline at end of file diff --git a/src/mol-geo/representation/volume/index.ts b/src/mol-geo/representation/volume/index.ts index f45b559ca306c07ece22256388ba25e9f12d2c0a..c1f0706afb51849a359ecca971b0b6245569cecb 100644 --- a/src/mol-geo/representation/volume/index.ts +++ b/src/mol-geo/representation/volume/index.ts @@ -27,7 +27,7 @@ export function VolumeRepresentation<P>(visualCtor: (volumeData: VolumeData) => _volumeData = volumeData const visual = visualCtor(_volumeData) await visual.create(ctx, _volumeData, props) - renderObjects.push(...visual.renderObjects) + renderObjects.push(visual.renderObject) }); } @@ -36,12 +36,8 @@ export function VolumeRepresentation<P>(visualCtor: (volumeData: VolumeData) => } return { - get renderObjects () { - return renderObjects - }, - get props () { - return _props - }, + get renderObjects () { return renderObjects }, + get props () { return _props }, create, update, getLoci(pickingId: PickingId) { diff --git a/src/mol-geo/representation/volume/surface.ts b/src/mol-geo/representation/volume/surface.ts index 14c40d182b1b855a6951d89ee2498406ab3a7f13..44906d165333f0350625b283cc7a0b89628a499a 100644 --- a/src/mol-geo/representation/volume/surface.ts +++ b/src/mol-geo/representation/volume/surface.ts @@ -10,7 +10,7 @@ import { Task, RuntimeContext } from 'mol-task' import { computeMarchingCubes } from '../../util/marching-cubes/algorithm'; import { Mesh } from '../../shape/mesh'; import { VolumeVisual } from '.'; -import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'; +import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'; import { fillSerial } from 'mol-gl/renderable/util'; import { ValueCell, defaults } from 'mol-util'; import { Mat4 } from 'mol-math/linear-algebra'; @@ -51,14 +51,12 @@ export const DefaultSurfaceProps = { export type SurfaceProps = Partial<typeof DefaultSurfaceProps> export default function SurfaceVisual(): VolumeVisual<SurfaceProps> { - const renderObjects: RenderObject[] = [] - let surface: MeshRenderObject + let renderObject: MeshRenderObject let curProps = DefaultSurfaceProps return { - renderObjects, + get renderObject () { return renderObject }, async create(ctx: RuntimeContext, volume: VolumeData, props: SurfaceProps = {}) { - renderObjects.length = 0 // clear props = { ...DefaultSurfaceProps, ...props } const mesh = await computeVolumeSurface(volume, curProps.isoValue).runAsChild(ctx) @@ -96,8 +94,7 @@ export default function SurfaceVisual(): VolumeVisual<SurfaceProps> { visible: defaults(props.visible, true) } - surface = createMeshRenderObject(values, state) - renderObjects.push(surface) + renderObject = createMeshRenderObject(values, state) }, async update(ctx: RuntimeContext, props: SurfaceProps) { // TODO diff --git a/src/mol-gl/renderable/mesh.ts b/src/mol-gl/renderable/mesh.ts index 562daec704a602fdbe4146b7bb4d9075f8a55a85..e18b88dab04d553e2dc8c21c3f3e93d627ca514a 100644 --- a/src/mol-gl/renderable/mesh.ts +++ b/src/mol-gl/renderable/mesh.ts @@ -7,7 +7,7 @@ import { Renderable, RenderableState, createRenderable } from '../renderable' import { Context } from '../webgl/context'; import { createRenderItem } from '../webgl/render-item'; -import { GlobalUniformSchema, BaseSchema, AttributeSpec, ElementsSpec, DefineSpec, Values, InternalSchema } from '../renderable/schema'; +import { GlobalUniformSchema, BaseSchema, AttributeSpec, ElementsSpec, DefineSpec, Values, InternalSchema } from './schema'; import { MeshShaderCode } from '../shader-code'; import { ValueCell } from 'mol-util'; diff --git a/src/mol-gl/renderable/point.ts b/src/mol-gl/renderable/point.ts index a200d4e40e3a1d4e6e8fc359f6276a2739b66f7a..e036a809f105e06b1951504ac7d5265f82e0934a 100644 --- a/src/mol-gl/renderable/point.ts +++ b/src/mol-gl/renderable/point.ts @@ -7,7 +7,7 @@ import { Renderable, RenderableState, createRenderable } from '../renderable' import { Context } from '../webgl/context'; import { createRenderItem } from '../webgl/render-item'; -import { GlobalUniformSchema, BaseSchema, AttributeSpec, UniformSpec, DefineSpec, Values, InternalSchema } from '../renderable/schema'; +import { GlobalUniformSchema, BaseSchema, AttributeSpec, UniformSpec, DefineSpec, Values, InternalSchema } from './schema'; import { PointShaderCode } from '../shader-code'; import { ValueCell } from 'mol-util'; diff --git a/src/mol-gl/shader/chunks/apply-fog.glsl b/src/mol-gl/shader/chunks/apply-fog.glsl index 9e176cbaa6c52918811d3493e139614abad5e4a0..0dcfbd63f071203c4e9f75c94fe7c25c352c5a55 100644 --- a/src/mol-gl/shader/chunks/apply-fog.glsl +++ b/src/mol-gl/shader/chunks/apply-fog.glsl @@ -1,6 +1,6 @@ -// #ifdef dUseFog +#ifdef dUseFog float depth = length(vViewPosition); // float depth = gl_FragCoord.z / gl_FragCoord.w; float fogFactor = smoothstep(uFogNear, uFogFar, depth); gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor); -// #endif \ No newline at end of file +#endif \ No newline at end of file diff --git a/src/mol-gl/webgl/render-item.ts b/src/mol-gl/webgl/render-item.ts index 721d4720e9147509950284e433bbe79a59c473d8..a9a8244b0571a8877afbb116b711e18902fcbe36 100644 --- a/src/mol-gl/webgl/render-item.ts +++ b/src/mol-gl/webgl/render-item.ts @@ -117,9 +117,11 @@ export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: S program.setUniforms(uniformValues) if (oesVertexArrayObject && vertexArray) { oesVertexArrayObject.bindVertexArrayOES(vertexArray) + // TODO need to bind elements buffer explicitely since it is not always recorded in the VAO + if (elementsBuffer) elementsBuffer.bind() } else { - program.bindAttributes(attributeBuffers) if (elementsBuffer) elementsBuffer.bind() + program.bindAttributes(attributeBuffers) } program.bindTextures(textures) if (elementsBuffer) { diff --git a/src/mol-gl/webgl/vertex-array.ts b/src/mol-gl/webgl/vertex-array.ts index eef6217aa50859de5b7cb7d86c440724dce539fc..aaf92f2754036dca4b0e5cbb89f13e5e6b748d9a 100644 --- a/src/mol-gl/webgl/vertex-array.ts +++ b/src/mol-gl/webgl/vertex-array.ts @@ -14,8 +14,8 @@ export function createVertexArray(ctx: Context, program: Program, attributeBuffe if (oesVertexArrayObject) { vertexArray = oesVertexArrayObject.createVertexArrayOES() oesVertexArrayObject.bindVertexArrayOES(vertexArray) - program.bindAttributes(attributeBuffers) if (elementsBuffer) elementsBuffer.bind() + program.bindAttributes(attributeBuffers) ctx.vaoCount += 1 oesVertexArrayObject.bindVertexArrayOES(null!) } diff --git a/src/mol-io/reader/cif/schema/mmcif.ts b/src/mol-io/reader/cif/schema/mmcif.ts index b87bee82a542b30d0290e52cd493f31477d48392..3640bb29ccfacb083ad9b982e2b31d54eea536b2 100644 --- a/src/mol-io/reader/cif/schema/mmcif.ts +++ b/src/mol-io/reader/cif/schema/mmcif.ts @@ -68,7 +68,7 @@ export const mmCIF_Schema = { mon_nstd_flag: Aliased<'no' | 'n' | 'yes' | 'y'>(str), name: str, type: Aliased<'D-peptide linking' | 'L-peptide linking' | 'D-peptide NH3 amino terminus' | 'L-peptide NH3 amino terminus' | 'D-peptide COOH carboxy terminus' | 'L-peptide COOH carboxy terminus' | 'DNA linking' | 'RNA linking' | 'L-RNA linking' | 'L-DNA linking' | 'DNA OH 5 prime terminus' | 'RNA OH 5 prime terminus' | 'DNA OH 3 prime terminus' | 'RNA OH 3 prime terminus' | 'D-saccharide 1,4 and 1,4 linking' | 'L-saccharide 1,4 and 1,4 linking' | 'D-saccharide 1,4 and 1,6 linking' | 'L-saccharide 1,4 and 1,6 linking' | 'L-saccharide' | 'D-saccharide' | 'saccharide' | 'non-polymer' | 'peptide linking' | 'peptide-like' | 'L-gamma-peptide, C-delta linking' | 'D-gamma-peptide, C-delta linking' | 'L-beta-peptide, C-gamma linking' | 'D-beta-peptide, C-gamma linking' | 'other'>(str), - pdbx_synonyms: str, + pdbx_synonyms: List(';', x => x), }, chem_comp_bond: { atom_id_1: str, diff --git a/src/mol-model/structure/export/mmcif.ts b/src/mol-model/structure/export/mmcif.ts index f09ee2103f814b5f7be1408602c05a39a9b65fa7..eca4f961ee652d1a817f7adce4359838e165b3f5 100644 --- a/src/mol-model/structure/export/mmcif.ts +++ b/src/mol-model/structure/export/mmcif.ts @@ -37,6 +37,7 @@ const Categories = [ copy_mmCif_category('exptl'), copy_mmCif_category('cell'), copy_mmCif_category('symmetry'), + copy_mmCif_category('chem_comp'), _entity, _atom_site ]; diff --git a/src/mol-model/structure/model/formats/mmcif.ts b/src/mol-model/structure/model/formats/mmcif.ts index 9dc2185554d02c35b7130e06baf8640f62f908b3..17b7ee9cf9f4df0ad0ab90bc398af82163679569 100644 --- a/src/mol-model/structure/model/formats/mmcif.ts +++ b/src/mol-model/structure/model/formats/mmcif.ts @@ -22,9 +22,11 @@ import { getIHMCoarse, EmptyIHMCoarse, IHMData } from './mmcif/ihm'; import { getSecondaryStructureMmCif } from './mmcif/secondary-structure'; import { getSequence } from './mmcif/sequence'; import { sortAtomSite } from './mmcif/sort'; +import { StructConn } from './mmcif/bonds/struct_conn'; +import { ChemicalComponent } from '../properties/chemical-component'; +import { ComponentType, getMoleculeType } from '../types'; import mmCIF_Format = Format.mmCIF -import { StructConn } from './mmcif/bonds/struct_conn'; type AtomSite = mmCIF_Database['atom_site'] @@ -99,6 +101,26 @@ function getAsymIdSerialMap(format: mmCIF_Format) { return map; } +function getChemicalComponentMap(format: mmCIF_Format) { + const map = new Map<string, ChemicalComponent>(); + const { id, type, name, pdbx_synonyms, formula, formula_weight } = format.data.chem_comp + for (let i = 0, il = id.rowCount; i < il; ++i) { + const _id = id.value(i) + const _type = type.value(i) + const cc: ChemicalComponent = { + id: _id, + type: ComponentType[_type], + moleculeType: getMoleculeType(_type, _id), + name: name.value(i), + synonyms: pdbx_synonyms.value(i), + formula: formula.value(i), + formulaWeight: formula_weight.value(i), + } + map.set(_id, cc) + } + return map +} + function createStandardModel(format: mmCIF_Format, atom_site: AtomSite, entities: Entities, previous?: Model): Model { const atomic = getAtomicHierarchyAndConformation(format, atom_site, entities, previous); if (previous && atomic.sameAsPrevious) { @@ -112,6 +134,7 @@ function createStandardModel(format: mmCIF_Format, atom_site: AtomSite, entities const modifiedResidueNameMap = getModifiedResidueNameMap(format); const asymIdSerialMap = getAsymIdSerialMap(format) + const chemicalComponentMap = getChemicalComponentMap(format) return { id: UUID.create(), @@ -128,7 +151,8 @@ function createStandardModel(format: mmCIF_Format, atom_site: AtomSite, entities properties: { secondaryStructure: getSecondaryStructureMmCif(format.data, atomic.hierarchy), modifiedResidueNameMap, - asymIdSerialMap + asymIdSerialMap, + chemicalComponentMap }, customProperties: new CustomProperties(), _staticPropertyData: Object.create(null), @@ -141,6 +165,7 @@ function createModelIHM(format: mmCIF_Format, data: IHMData): Model { const coarse = getIHMCoarse(data); const modifiedResidueNameMap = getModifiedResidueNameMap(format); const asymIdSerialMap = getAsymIdSerialMap(format) + const chemicalComponentMap = getChemicalComponentMap(format) return { id: UUID.create(), @@ -157,7 +182,8 @@ function createModelIHM(format: mmCIF_Format, data: IHMData): Model { properties: { secondaryStructure: getSecondaryStructureMmCif(format.data, atomic.hierarchy), modifiedResidueNameMap, - asymIdSerialMap + asymIdSerialMap, + chemicalComponentMap }, customProperties: new CustomProperties(), _staticPropertyData: Object.create(null), @@ -233,7 +259,7 @@ async function readIHM(ctx: RuntimeContext, format: mmCIF_Format) { }; const model = createModelIHM(format, data); attachProps(model); - models.push(createModelIHM(format, data)); + models.push(model); } return models; diff --git a/src/mol-model/structure/model/formats/mmcif/atomic.ts b/src/mol-model/structure/model/formats/mmcif/atomic.ts index f0b0480fb2110e1f39ef422bb4a7f7534615ab67..736c81fe63d4fcc2a062462e22c8088f7a1dfb9f 100644 --- a/src/mol-model/structure/model/formats/mmcif/atomic.ts +++ b/src/mol-model/structure/model/formats/mmcif/atomic.ts @@ -20,10 +20,10 @@ import mmCIF_Format = Format.mmCIF type AtomSite = mmCIF_Database['atom_site'] function findHierarchyOffsets(atom_site: AtomSite) { - if (atom_site._rowCount === 0) return { residues: [], chains: [] }; + if (atom_site._rowCount === 0) return { residues: [], polymers: [], chains: [] }; const start = 0, end = atom_site._rowCount; - const residues = [start as Element], chains = [start as Element]; + const residues = [start as Element], chains = [start as Element], polymers = [start as Element]; const { label_entity_id, label_asym_id, label_seq_id, auth_seq_id, pdbx_PDB_ins_code, label_comp_id } = atom_site; @@ -34,11 +34,13 @@ function findHierarchyOffsets(atom_site: AtomSite) { || !auth_seq_id.areValuesEqual(i - 1, i) || !pdbx_PDB_ins_code.areValuesEqual(i - 1, i) || !label_comp_id.areValuesEqual(i - 1, i); + const newPolymer = newResidue && label_seq_id.value(i - 1) !== label_seq_id.value(i) - 1; if (newResidue) residues[residues.length] = i as Element; + if (newPolymer) polymers[polymers.length] = i as Element; if (newChain) chains[chains.length] = i as Element; } - return { residues, chains }; + return { residues, polymers, chains }; } function createHierarchyData(atom_site: AtomSite, offsets: { residues: ArrayLike<number>, chains: ArrayLike<number> }): AtomicData { @@ -92,6 +94,7 @@ export function getAtomicHierarchyAndConformation(format: mmCIF_Format, atom_sit const hierarchySegments: AtomicSegments = { residueSegments: Segmentation.ofOffsets(hierarchyOffsets.residues, Interval.ofBounds(0, atom_site._rowCount)), chainSegments: Segmentation.ofOffsets(hierarchyOffsets.chains, Interval.ofBounds(0, atom_site._rowCount)), + polymerSegments: Segmentation.ofOffsets(hierarchyOffsets.polymers, Interval.ofBounds(0, atom_site._rowCount)), } const hierarchyKeys = getAtomicKeys(hierarchyData, entities, hierarchySegments); diff --git a/src/mol-model/structure/model/formats/mmcif/ihm.ts b/src/mol-model/structure/model/formats/mmcif/ihm.ts index db2e86c84014a70e261ec700a16c9c2512cbcfc6..1c93c716194329740575cde23028e263816f727e 100644 --- a/src/mol-model/structure/model/formats/mmcif/ihm.ts +++ b/src/mol-model/structure/model/formats/mmcif/ihm.ts @@ -28,6 +28,8 @@ export const EmptyIHMCoarse = { hierarchy: CoarseHierarchy.Empty, conformation: export function getIHMCoarse(data: IHMData): { hierarchy: CoarseHierarchy, conformation: CoarseConformation } { const { ihm_sphere_obj_site, ihm_gaussian_obj_site } = data; + if (ihm_sphere_obj_site._rowCount === 0 && ihm_gaussian_obj_site._rowCount === 0) return EmptyIHMCoarse; + const sphereData = getData(ihm_sphere_obj_site); const sphereConformation = getSphereConformation(ihm_sphere_obj_site); const sphereKeys = getCoarseKeys(sphereData, data.entities); @@ -78,16 +80,24 @@ function getGaussianConformation(data: mmCIF['ihm_gaussian_obj_site']): CoarseGa }; } -function getChainSegments(asym_id: Column<string>) { - const offsets = [0 as Element]; +function getSegments(asym_id: Column<string>, seq_id_begin: Column<number>, seq_id_end: Column<number>) { + const polymerOffsets = [0 as Element], chainOffsets = [0 as Element]; for (let i = 1, _i = asym_id.rowCount; i < _i; i++) { - if (!asym_id.areValuesEqual(i - 1, i)) offsets[offsets.length] = i as Element; + const newChain = !asym_id.areValuesEqual(i - 1, i); + const newPolymer = newChain + || seq_id_end.value(i - 1) !== seq_id_begin.value(i) - 1; + + if (newPolymer) polymerOffsets[polymerOffsets.length] = i as Element; + if (newChain) chainOffsets[chainOffsets.length] = i as Element; } - return Segmentation.ofOffsets(offsets, Interval.ofBounds(0, asym_id.rowCount)); + return { + chainSegments: Segmentation.ofOffsets(chainOffsets, Interval.ofBounds(0, asym_id.rowCount)), + polymerSegments: Segmentation.ofOffsets(polymerOffsets, Interval.ofBounds(0, asym_id.rowCount)) + } } function getData(data: mmCIF['ihm_sphere_obj_site'] | mmCIF['ihm_gaussian_obj_site']): CoarseElementData { const { entity_id, seq_id_begin, seq_id_end, asym_id } = data; - return { count: entity_id.rowCount, entity_id, asym_id, seq_id_begin, seq_id_end, chainSegments: getChainSegments(asym_id) }; + return { count: entity_id.rowCount, entity_id, asym_id, seq_id_begin, seq_id_end, ...getSegments(asym_id, seq_id_begin, seq_id_end) }; } \ No newline at end of file diff --git a/src/mol-model/structure/model/model.ts b/src/mol-model/structure/model/model.ts index e83ed86d3591283395a1efce657fe0d9a57d0acf..24e5a5875ecb7006e999f998339933509fc97101 100644 --- a/src/mol-model/structure/model/model.ts +++ b/src/mol-model/structure/model/model.ts @@ -14,8 +14,9 @@ import { Entities } from './properties/common'; import { CustomProperties } from './properties/custom'; import { SecondaryStructure } from './properties/seconday-structure'; -// import from_gro from './formats/gro' import from_mmCIF from './formats/mmcif' +import { ChemicalComponent } from './properties/chemical-component'; + /** * Interface to the "source data" of the molecule. * @@ -38,11 +39,14 @@ interface Model extends Readonly<{ atomicConformation: AtomicConformation, properties: { - // secondary structure provided by the input file + /** secondary structure provided by the input file */ readonly secondaryStructure: SecondaryStructure, - // maps modified residue name to its parent + /** maps modified residue name to its parent */ readonly modifiedResidueNameMap: Map<string, string>, + /** maps asym id to unique serial number */ readonly asymIdSerialMap: Map<string, number> + /** maps residue name to `ChemicalComponent` data */ + readonly chemicalComponentMap: Map<string, ChemicalComponent> }, customProperties: CustomProperties, diff --git a/src/mol-model/structure/model/properties/atomic/hierarchy.ts b/src/mol-model/structure/model/properties/atomic/hierarchy.ts index f03c8521349e6fa316d2b97c870ffad4b7425920..12c9de4c1ed01dfbaaa055a7d2e7756ced2ed743 100644 --- a/src/mol-model/structure/model/properties/atomic/hierarchy.ts +++ b/src/mol-model/structure/model/properties/atomic/hierarchy.ts @@ -51,7 +51,12 @@ export interface AtomicSegments { /** Maps residueIndex to a range of atoms [segments[rI], segments[rI + 1]) */ residueSegments: Segmentation<Element>, /** Maps chainIndex to a range of atoms [segments[cI], segments[cI + 1]) */ - chainSegments: Segmentation<Element> + chainSegments: Segmentation<Element>, + /** + * bonded/connected stretches of polymer chains, i.e. a chain will be + * broken into multiple polymer segments if there are missing residues + */ + polymerSegments: Segmentation<Element> // TODO: include entity segments? } diff --git a/src/mol-model/structure/model/properties/chemical-component.ts b/src/mol-model/structure/model/properties/chemical-component.ts new file mode 100644 index 0000000000000000000000000000000000000000..beac7f107ba10197eebd5034eff6d994a37cc57a --- /dev/null +++ b/src/mol-model/structure/model/properties/chemical-component.ts @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { MoleculeType, ComponentType } from '../types' + +export interface ChemicalComponent { + id: string + type: ComponentType + moleculeType: MoleculeType + name: string + synonyms: string[] + formula: string + formulaWeight: number +} diff --git a/src/mol-model/structure/model/properties/coarse/hierarchy.ts b/src/mol-model/structure/model/properties/coarse/hierarchy.ts index 86071cbc54be4e9d8b6968e381202e764d87125f..57d3da0acaac976009976324824438a94ae7ee2e 100644 --- a/src/mol-model/structure/model/properties/coarse/hierarchy.ts +++ b/src/mol-model/structure/model/properties/coarse/hierarchy.ts @@ -27,7 +27,12 @@ export interface CoarseElementData { seq_id_begin: Column<number>, seq_id_end: Column<number>, - chainSegments: Segmentation<Element> + chainSegments: Segmentation<Element>, + /** + * bonded/connected stretches of polymer chains, i.e. a chain will be + * broken into multiple polymer segments if there are missing residues + */ + polymerSegments: Segmentation<Element> } export type CoarseElements = CoarsedElementKeys & CoarseElementData diff --git a/src/mol-model/structure/model/properties/utils/coarse-keys.ts b/src/mol-model/structure/model/properties/utils/coarse-keys.ts index da591779c8d5c17a111c7686686cd78b2156e127..8ea3f848ca2a2117f11df7f6f41cbcec68318462 100644 --- a/src/mol-model/structure/model/properties/utils/coarse-keys.ts +++ b/src/mol-model/structure/model/properties/utils/coarse-keys.ts @@ -31,6 +31,7 @@ function createLookUp(entities: Entities, chain: Map<number, Map<string, number> if (!cm.has(c)) return -1; return cm.get(c)!; } + // TODO consider implementing as binary search const findSequenceKey: CoarsedElementKeys['findSequenceKey'] = (e, c, s) => { const eKey = getEntKey(e); if (eKey < 0) return -1; diff --git a/src/mol-model/structure/model/types.ts b/src/mol-model/structure/model/types.ts index a9b7483bf4d86b96b74c1d0a4bde09931fb2f3ba..127ecbec5623c9b38d108d09a6b21d47ef3e821e 100644 --- a/src/mol-model/structure/model/types.ts +++ b/src/mol-model/structure/model/types.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> * @author David Sehnal <david.sehnal@gmail.com> @@ -30,63 +30,137 @@ export function ElementSymbol(s: string): ElementSymbol { return _esCache[s] || s.toUpperCase(); } +/** Entity types as defined in the mmCIF dictionary */ export const enum EntityType { - Unknown = 'unknown', - Polymer = 'polymer', - NonPolymer = 'non-polymer', - Macrolide = 'macrolide', - Water = 'water' + 'unknown', 'polymer', 'non-polymer', 'macrolide', 'water' } export const enum MoleculeType { - Unknown, - Water, - Ion, - Protein, + /** the molecule type is not known */ + unknown, + /** a known, but here not listed molecule type */ + other, + /** water molecule */ + water, + /** small ionic molecule */ + ion, + /** protein, e.g. component type included in `ProteinComponentTypeNames` */ + protein, + /** RNA, e.g. component type included in `RNAComponentTypeNames` */ RNA, + /** DNA, e.g. component type included in `DNAComponentTypeNames` */ DNA, - Saccharide + /** sacharide, e.g. component type included in `SaccharideComponentTypeNames` */ + saccharide } -export const enum BackboneType { - Unknown, - Protein, - RNA, - DNA, - CgProtein, - CgRNA, - CgDNA +/** Chemical component types as defined in the mmCIF CCD */ +export enum ComponentType { + // protein + 'D-peptide linking', 'L-peptide linking', 'D-peptide NH3 amino terminus', + 'L-peptide NH3 amino terminus', 'D-peptide COOH carboxy terminus', + 'L-peptide COOH carboxy terminus', 'peptide linking', 'peptide-like', + 'L-gamma-peptide, C-delta linking', 'D-gamma-peptide, C-delta linking', + 'L-beta-peptide, C-gamma linking', 'D-beta-peptide, C-gamma linking', + + // DNA + 'DNA linking', 'L-DNA linking', 'DNA OH 5 prime terminus', 'DNA OH 3 prime terminus', + + // RNA + 'RNA linking', 'L-RNA linking', 'RNA OH 5 prime terminus', 'RNA OH 3 prime terminus', + + // sacharide + 'D-saccharide 1,4 and 1,4 linking', 'L-saccharide 1,4 and 1,4 linking', + 'D-saccharide 1,4 and 1,6 linking', 'L-saccharide 1,4 and 1,6 linking', 'L-saccharide', + 'D-saccharide', 'saccharide', + + 'non-polymer', 'other' } -const _chemCompNonPolymer = ['NON-POLYMER']; -const _chemCompOther = ['OTHER']; -const _chemCompSaccharide = [ - 'D-SACCHARIDE', 'D-SACCHARIDE 1,4 AND 1,4 LINKING', 'D-SACCHARIDE 1,4 AND 1,6 LINKING', - 'L-SACCHARIDE', 'L-SACCHARIDE 1,4 AND 1,4 LINKING', 'L-SACCHARIDE 1,4 AND 1,6 LINKING', - 'SACCHARIDE' -]; - -export const ChemComp = { - Protein: [ - 'D-BETA-PEPTIDE, C-GAMMA LINKING', 'D-GAMMA-PEPTIDE, C-DELTA LINKING', - 'D-PEPTIDE COOH CARBOXY TERMINUS', 'D-PEPTIDE NH3 AMINO TERMINUS', 'D-PEPTIDE LINKING', - 'L-BETA-PEPTIDE, C-GAMMA LINKING', 'L-GAMMA-PEPTIDE, C-DELTA LINKING', - 'L-PEPTIDE COOH CARBOXY TERMINUS', 'L-PEPTIDE NH3 AMINO TERMINUS', 'L-PEPTIDE LINKING', - 'PEPTIDE LINKING', 'PEPTIDE-LIKE' - ], - RNA: [ - 'RNA OH 3 PRIME TERMINUS', 'RNA OH 5 PRIME TERMINUS', 'RNA LINKING' - ], - DNA: [ - 'DNA OH 3 PRIME TERMINUS', 'DNA OH 5 PRIME TERMINUS', 'DNA LINKING', - 'L-DNA LINKING', 'L-RNA LINKING' - ], - Saccharide: _chemCompSaccharide, - Other: _chemCompOther, - NonPolymer: _chemCompNonPolymer, - Hetero: _chemCompNonPolymer.concat(_chemCompOther, _chemCompSaccharide) +/** Chemical component type names for protein */ +export const ProteinComponentTypeNames = [ + 'D-peptide linking', 'L-peptide linking', 'D-peptide NH3 amino terminus', + 'L-peptide NH3 amino terminus', 'D-peptide COOH carboxy terminus', + 'L-peptide COOH carboxy terminus', 'peptide linking', 'peptide-like', + 'L-gamma-peptide, C-delta linking', 'D-gamma-peptide, C-delta linking', + 'L-beta-peptide, C-gamma linking', 'D-beta-peptide, C-gamma linking', +] + +/** Chemical component type names for DNA */ +export const DNAComponentTypeNames = [ + 'DNA linking', 'L-DNA linking', 'DNA OH 5 prime terminus', 'DNA OH 3 prime terminus', +] + +/** Chemical component type names for RNA */ +export const RNAComponentTypeNames = [ + 'RNA linking', 'L-RNA linking', 'RNA OH 5 prime terminus', 'RNA OH 3 prime terminus', +] + +/** Chemical component type names for saccharide */ +export const SaccharideComponentTypeNames = [ + 'D-saccharide 1,4 and 1,4 linking', 'L-saccharide 1,4 and 1,4 linking', + 'D-saccharide 1,4 and 1,6 linking', 'L-saccharide 1,4 and 1,6 linking', 'L-saccharide', + 'D-saccharide', 'saccharide', +] + +/** Common names for water molecules */ +export const WaterNames = [ + 'SOL', 'WAT', 'HOH', 'H2O', 'W', 'DOD', 'D3O', 'TIP3', 'TIP4', 'SPC' +] + +/** get the molecule type from component type and id */ +export function getMoleculeType(compType: string, compId: string) { + if (ProteinComponentTypeNames.includes(compType)) { + return MoleculeType.protein + } else if (RNAComponentTypeNames.includes(compType)) { + return MoleculeType.RNA + } else if (DNAComponentTypeNames.includes(compType)) { + return MoleculeType.DNA + } else if (SaccharideComponentTypeNames.includes(compType)) { + return MoleculeType.saccharide + } else if (WaterNames.includes(compId)) { + return MoleculeType.water + } else if (IonNames.includes(compId)) { + return MoleculeType.ion + } else { + return MoleculeType.unknown + } } +/** + * all chemical components with the word "ion" in their name, Sep 2016 + * + * SET SESSION group_concat_max_len = 1000000; + * SELECT GROUP_CONCAT(id_ ORDER BY id_ ASC SEPARATOR '", "') from + * ( + * SELECT count(obj_id) as c, id_ + * FROM pdb.chem_comp WHERE name LIKE "% ION%" + * GROUP BY id_ + * ) AS t1; + */ +export const IonNames = [ + '118', '119', '1AL', '1CU', '2FK', '2HP', '2OF', '3CO', + '3MT', '3NI', '3OF', '3P8', '4MO', '4PU', '543', '6MO', 'ACT', 'AG', 'AL', + 'ALF', 'AM', 'ATH', 'AU', 'AU3', 'AUC', 'AZI', 'BA', 'BCT', 'BEF', 'BF4', 'BO4', + 'BR', 'BS3', 'BSY', 'CA', 'CAC', 'CD', 'CD1', 'CD3', 'CD5', 'CE', 'CHT', 'CL', + 'CO', 'CO3', 'CO5', 'CON', 'CR', 'CS', 'CSB', 'CU', 'CU1', 'CU3', 'CUA', 'CUZ', + 'CYN', 'DME', 'DMI', 'DSC', 'DTI', 'DY', 'E4N', 'EDR', 'EMC', 'ER3', 'EU', + 'EU3', 'F', 'FE', 'FE2', 'FPO', 'GA', 'GD3', 'GEP', 'HAI', 'HG', 'HGC', 'IN', + 'IOD', 'IR', 'IR3', 'IRI', 'IUM', 'K', 'KO4', 'LA', 'LCO', 'LCP', 'LI', 'LU', + 'MAC', 'MG', 'MH2', 'MH3', 'MLI', 'MLT', 'MMC', 'MN', 'MN3', 'MN5', 'MN6', + 'MO1', 'MO2', 'MO3', 'MO4', 'MO5', 'MO6', 'MOO', 'MOS', 'MOW', 'MW1', 'MW2', + 'MW3', 'NA', 'NA2', 'NA5', 'NA6', 'NAO', 'NAW', 'NCO', 'NET', 'NH4', 'NI', + 'NI1', 'NI2', 'NI3', 'NO2', 'NO3', 'NRU', 'O4M', 'OAA', 'OC1', 'OC2', 'OC3', + 'OC4', 'OC5', 'OC6', 'OC7', 'OC8', 'OCL', 'OCM', 'OCN', 'OCO', 'OF1', 'OF2', + 'OF3', 'OH', 'OS', 'OS4', 'OXL', 'PB', 'PBM', 'PD', 'PDV', 'PER', 'PI', 'PO3', + 'PO4', 'PR', 'PT', 'PT4', 'PTN', 'RB', 'RH3', 'RHD', 'RU', 'SB', 'SCN', 'SE4', + 'SEK', 'SM', 'SMO', 'SO3', 'SO4', 'SR', 'T1A', 'TB', 'TBA', 'TCN', 'TEA', 'TH', + 'THE', 'TL', 'TMA', 'TRA', 'UNX', 'V', 'VN3', 'VO4', 'W', 'WO5', 'Y1', 'YB', + 'YB2', 'YH', 'YT3', 'ZCM', 'ZN', 'ZN2', 'ZN3', 'ZNO', 'ZO3', + // additional ion names + 'OHX' +] + export interface SecondaryStructureType extends BitFlags<SecondaryStructureType.Flag> { } export namespace SecondaryStructureType { export const Helix = ['h', 'g', 'i'] diff --git a/src/mol-model/structure/structure/properties.ts b/src/mol-model/structure/structure/properties.ts index 5a7b9e4d8971e9e1c30781c35d6a72ab87f96667..d13f5ddd88a4de7f85ee503e589925e244e0d621 100644 --- a/src/mol-model/structure/structure/properties.ts +++ b/src/mol-model/structure/structure/properties.ts @@ -89,7 +89,16 @@ const coarse = { gaussian_covariance_matrix: Element.property(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.coarseConformation.covariance_matrix[l.element]) } -function eK(l: Element.Location) { return !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.entityKey[l.unit.chainIndex[l.element]]; } +function eK(l: Element.Location) { + switch (l.unit.kind) { + case Unit.Kind.Atomic: + return l.unit.model.atomicHierarchy.entityKey[l.unit.chainIndex[l.element]] + case Unit.Kind.Spheres: + return l.unit.model.coarseHierarchy.spheres.entityKey[l.element] + case Unit.Kind.Gaussians: + return l.unit.model.coarseHierarchy.gaussians.entityKey[l.element] + } +} const entity = { key: eK, diff --git a/src/mol-model/structure/structure/unit/links/inter-compute.ts b/src/mol-model/structure/structure/unit/links/inter-compute.ts index bc1dd6becd6a954511d6b10948acaea5df43d999..d96698455f54d48f84afac353ce4a7339c1aee8d 100644 --- a/src/mol-model/structure/structure/unit/links/inter-compute.ts +++ b/src/mol-model/structure/structure/unit/links/inter-compute.ts @@ -12,6 +12,7 @@ import { getElementIdx, getElementPairThreshold, getElementThreshold, isHydrogen import { InterUnitBonds } from './data'; import { UniqueArray } from 'mol-data/generic'; import { SortedArray } from 'mol-data/int'; +import { Vec3, Mat4 } from 'mol-math/linear-algebra'; const MAX_RADIUS = 4; @@ -34,11 +35,14 @@ function addLink(indexA: number, indexB: number, order: number, flag: LinkType.F UniqueArray.add(state.bondedB, indexB, indexB); } +const _imageTransform = Mat4.zero(); + function findPairLinks(unitA: Unit.Atomic, unitB: Unit.Atomic, params: LinkComputationParameters, map: Map<number, InterUnitBonds.UnitPairBonds[]>) { const state: PairState = { mapAB: new Map(), mapBA: new Map(), bondedA: UniqueArray.create(), bondedB: UniqueArray.create() }; let bondCount = 0; - const { elements: atomsA, conformation: { x, y, z } } = unitA; + const { elements: atomsA } = unitA; + const { x: xA, y: yA, z: zA } = unitA.model.atomicConformation; const { elements: atomsB } = unitB; const atomCount = unitA.elements.length; @@ -47,17 +51,15 @@ function findPairLinks(unitA: Unit.Atomic, unitB: Unit.Atomic, params: LinkCompu const { lookup3d } = unitB; const structConn = unitA.model === unitB.model && unitA.model.sourceData.kind === 'mmCIF' ? StructConn.get(unitA.model) : void 0; + // the lookup queries need to happen in the "unitB space". + // that means imageA = inverseOperB(operA(aI)) + const imageTransform = Mat4.mul(_imageTransform, unitB.conformation.operator.inverse, unitA.conformation.operator.matrix); + const imageA = Vec3.zero(); + for (let _aI = 0; _aI < atomCount; _aI++) { const aI = atomsA[_aI]; - const aeI = getElementIdx(type_symbolA.value(aI)); - const { indices, count, squaredDistances } = lookup3d.find(x(aI), y(aI), z(aI), MAX_RADIUS); - const isHa = isHydrogen(aeI); - const thresholdA = getElementThreshold(aeI); - const altA = label_alt_idA.value(aI); - const metalA = MetalsSet.has(aeI); const structConnEntries = params.forceCompute ? void 0 : structConn && structConn.getAtomEntries(aI); - if (structConnEntries) { for (const se of structConnEntries) { if (se.distance < MAX_RADIUS) continue; @@ -71,6 +73,18 @@ function findPairLinks(unitA: Unit.Atomic, unitB: Unit.Atomic, params: LinkCompu } } + const aeI = getElementIdx(type_symbolA.value(aI)); + + Vec3.set(imageA, xA[aI], yA[aI], zA[aI]); + Vec3.transformMat4(imageA, imageA, imageTransform); + const { indices, count, squaredDistances } = lookup3d.find(imageA[0], imageA[1], imageA[2], MAX_RADIUS); + if (count === 0) continue; + + const isHa = isHydrogen(aeI); + const thresholdA = getElementThreshold(aeI); + const altA = label_alt_idA.value(aI); + const metalA = MetalsSet.has(aeI); + for (let ni = 0; ni < count; ni++) { const _bI = indices[ni]; const bI = atomsB[_bI]; diff --git a/src/mol-task/task.ts b/src/mol-task/task.ts index 92618a09155ca0b42fa34b8d233b84916723fbe5..7fdf1b36359763018cd7f0d3d4cdba4ddc13379a 100644 --- a/src/mol-task/task.ts +++ b/src/mol-task/task.ts @@ -7,7 +7,7 @@ import { RuntimeContext } from './execution/runtime-context' import { Progress } from './execution/progress' import { ExecuteObservable, ExecuteObservableChild, ExecuteInContext } from './execution/observable'; -import { SyncRuntimeContext } from 'mol-task/execution/synchronous'; +import { SyncRuntimeContext } from './execution/synchronous'; import { idFactory } from 'mol-util/id-factory'; /** A "named function wrapper" with built in "computation tree progress tracking". */ diff --git a/src/mol-util/array.ts b/src/mol-util/array.ts new file mode 100644 index 0000000000000000000000000000000000000000..36bb291267f2ad65324d86548d40dacfca5c5e34 --- /dev/null +++ b/src/mol-util/array.ts @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +export function arrayMax(array: Helpers.NumberArray) { + let max = -Infinity + for (let i = 0, il = array.length; i < il; ++i) { + if (array[i] > max) max = array[i] + } + return max +} + +export function arrayMin(array: Helpers.NumberArray) { + let min = Infinity + for (let i = 0, il = array.length; i < il; ++i) { + if (array[i] < min) min = array[i] + } + return min +} + +export function arraySum(array: Helpers.NumberArray, stride = 1, offset = 0) { + const n = array.length + let sum = 0 + for (let i = offset; i < n; i += stride) { + sum += array[i] + } + return sum +} + +export function arrayMean(array: Helpers.NumberArray, stride = 1, offset = 0) { + return arraySum(array, stride, offset) / (array.length / stride) +} + +export function arrayRms(array: Helpers.NumberArray) { + const n = array.length + let sumSq = 0 + for (let i = 0; i < n; ++i) { + const di = array[i] + sumSq += di * di + } + return Math.sqrt(sumSq / n) +} \ No newline at end of file diff --git a/src/mol-view/label.ts b/src/mol-view/label.ts index 1623b27a95f0937b6b67060af6168bf0ca44bb17..e2c54c34f644e3e5b0f989ee837e165510fc453e 100644 --- a/src/mol-view/label.ts +++ b/src/mol-view/label.ts @@ -61,7 +61,7 @@ export function elementLabel(loc: Element.Location) { if (seq_id_begin === seq_id_end) { const entityKey = Props.coarse.entityKey(loc) const seq = loc.unit.model.sequence.byEntityKey[entityKey] - const comp_id = seq.compId.value(seq_id_begin) + const comp_id = seq.compId.value(seq_id_begin - 1) // 1-indexed element = `[${comp_id}]${seq_id_begin}:${asym_id}` } else { element = `${seq_id_begin}-${seq_id_end}:${asym_id}` diff --git a/src/mol-view/stage.ts b/src/mol-view/stage.ts index 964b42c5c51487265fd9ea392c8d26215a24e3a7..c4eb99cd0da1e542910afb4b63dfebcf2b878c97 100644 --- a/src/mol-view/stage.ts +++ b/src/mol-view/stage.ts @@ -4,14 +4,18 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import Viewer from 'mol-view/viewer' +import Viewer from './viewer' import { StateContext } from './state/context'; import { Progress } from 'mol-task'; -import { MmcifUrlToModel, ModelToStructure, StructureToSpacefill, StructureToBallAndStick, StructureToDistanceRestraint } from './state/transform'; +import { MmcifUrlToModel, ModelToStructure, StructureToSpacefill, StructureToBallAndStick, StructureToDistanceRestraint, StructureToCartoon, StructureToBackbone } from './state/transform'; import { UrlEntity } from './state/entity'; import { SpacefillProps } from 'mol-geo/representation/structure/spacefill'; import { Context } from 'mol-app/context/context'; import { BallAndStickProps } from 'mol-geo/representation/structure/ball-and-stick'; +import { CartoonProps } from 'mol-geo/representation/structure/cartoon'; +import { DistanceRestraintProps } from 'mol-geo/representation/structure/distance-restraint'; +import { BackboneProps } from 'mol-geo/representation/structure/backbone'; +import { Queries as Q, StructureProperties as SP, Query, Selection } from 'mol-model/structure'; const spacefillProps: SpacefillProps = { doubleSided: true, @@ -23,7 +27,30 @@ const spacefillProps: SpacefillProps = { const ballAndStickProps: BallAndStickProps = { doubleSided: true, colorTheme: { name: 'chain-id' }, - sizeTheme: { name: 'uniform', value: 0.25 }, + sizeTheme: { name: 'uniform', value: 0.05 }, + linkRadius: 0.05, + quality: 'auto', + useFog: false +} + +const distanceRestraintProps: DistanceRestraintProps = { + doubleSided: true, + colorTheme: { name: 'chain-id' }, + linkRadius: 0.5, + quality: 'auto', + useFog: false +} + +const backboneProps: BackboneProps = { + doubleSided: true, + colorTheme: { name: 'chain-id' }, + quality: 'auto', + useFog: false +} + +const cartoonProps: CartoonProps = { + doubleSided: true, + colorTheme: { name: 'chain-id' }, quality: 'auto', useFog: false } @@ -43,14 +70,29 @@ export class Stage { // this.loadPdbid('1jj2') // this.loadPdbid('4umt') // ligand has bond with order 3 - this.loadPdbid('1crn') // small - // this.loadPdbid('1rb8') // virus + // this.loadPdbid('1crn') // small + this.loadPdbid('1hrv') // viral assembly + // this.loadPdbid('1rb8') // virus TODO funky inter unit bonds rendering // this.loadPdbid('1blu') // metal coordination // this.loadPdbid('3pqr') // inter unit bonds // this.loadPdbid('4v5a') // ribosome + // this.loadPdbid('3j3q') // ... + // this.loadPdbid('3sn6') // discontinuous chains // this.loadMmcifUrl(`../../examples/1cbs_full.bcif`) - // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000001.cif`) + // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000001.cif`) // ok + // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000002.cif`) // ok + // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000003.cif`) // ok + // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000004.cif`) // TODO issue with cross-link extraction + // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000005.cif`) // TODO only three spacefill atoms rendered + // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000006.cif`) // TODO only three spacefill atoms rendered + // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000007.cif`) // TODO only three spacefill atoms rendered + // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000008.cif`) // ok + // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000010.cif`) // ok + // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000011.cif`) // ok + // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000012.cif`) // ok + // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000014.cif`) // ok + // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000016.cif`) // TODO only three spacefill atoms rendered } async loadMmcifUrl (url: string) { @@ -58,11 +100,21 @@ export class Stage { const modelEntity = await MmcifUrlToModel.apply(this.ctx, urlEntity) const structureEntity = await ModelToStructure.apply(this.ctx, modelEntity) - StructureToSpacefill.apply(this.ctx, structureEntity, { ...spacefillProps, visible: true }) - StructureToBallAndStick.apply(this.ctx, structureEntity, ballAndStickProps) - StructureToDistanceRestraint.apply(this.ctx, structureEntity, ballAndStickProps) + StructureToBallAndStick.apply(this.ctx, structureEntity, { ...ballAndStickProps, visible: true }) + StructureToSpacefill.apply(this.ctx, structureEntity, { ...spacefillProps, visible: false }) + StructureToDistanceRestraint.apply(this.ctx, structureEntity, { ...distanceRestraintProps, visible: false }) + // StructureToBackbone.apply(this.ctx, structureEntity, { ...backboneProps, visible: true }) + StructureToCartoon.apply(this.ctx, structureEntity, { ...cartoonProps, visible: false }) this.globalContext.components.sequenceView.setState({ structure: structureEntity.value }); + + const structureEntity2 = await ModelToStructure.apply(this.ctx, modelEntity) + const q1 = Q.generators.atoms({ + residueTest: l => SP.residue.label_seq_id(l) > 30 + }); + structureEntity2.value = Selection.unionStructure(await Query(q1)(structureEntity2.value).run()); + StructureToBackbone.apply(this.ctx, structureEntity2, { ...backboneProps, visible: true }) + StructureToCartoon.apply(this.ctx, structureEntity2, { ...cartoonProps, visible: true }) } loadPdbid (pdbid: string) { diff --git a/src/mol-view/state/entity.ts b/src/mol-view/state/entity.ts index d106b4f764649db48feb450e380f615a1156a0ed..75643ee09446ad20bc17e754dbecf9f7b4904737 100644 --- a/src/mol-view/state/entity.ts +++ b/src/mol-view/state/entity.ts @@ -15,6 +15,8 @@ import { StructureRepresentation } from 'mol-geo/representation/structure'; import { SpacefillProps } from 'mol-geo/representation/structure/spacefill'; import { BallAndStickProps } from 'mol-geo/representation/structure/ball-and-stick'; import { DistanceRestraintProps } from 'mol-geo/representation/structure/distance-restraint'; +import { CartoonProps } from 'mol-geo/representation/structure/cartoon'; +import { BackboneProps } from 'mol-geo/representation/structure/backbone'; const getNextId = idFactory(1) @@ -135,4 +137,18 @@ export namespace DistanceRestraintEntity { export function ofRepr(ctx: StateContext, repr: StructureRepresentation<DistanceRestraintProps>): DistanceRestraintEntity { return StateEntity.create(ctx, 'distancerestraint', repr ) } +} + +export type BackboneEntity = StateEntity<StructureRepresentation<BackboneProps>, 'backbone'> +export namespace BackboneEntity { + export function ofRepr(ctx: StateContext, repr: StructureRepresentation<BackboneProps>): BackboneEntity { + return StateEntity.create(ctx, 'backbone', repr ) + } +} + +export type CartoonEntity = StateEntity<StructureRepresentation<CartoonProps>, 'cartoon'> +export namespace CartoonEntity { + export function ofRepr(ctx: StateContext, repr: StructureRepresentation<CartoonProps>): CartoonEntity { + return StateEntity.create(ctx, 'cartoon', repr ) + } } \ No newline at end of file diff --git a/src/mol-view/state/transform.ts b/src/mol-view/state/transform.ts index 13c6d2c8fd8afdd24f67576fee91264f115b951c..23d96b188c02269d4ca4e7e338178d99f90fae8c 100644 --- a/src/mol-view/state/transform.ts +++ b/src/mol-view/state/transform.ts @@ -5,7 +5,7 @@ */ import CIF from 'mol-io/reader/cif' -import { FileEntity, DataEntity, UrlEntity, CifEntity, MmcifEntity, ModelEntity, StructureEntity, SpacefillEntity, AnyEntity, NullEntity, BallAndStickEntity, DistanceRestraintEntity } from './entity'; +import { FileEntity, DataEntity, UrlEntity, CifEntity, MmcifEntity, ModelEntity, StructureEntity, SpacefillEntity, AnyEntity, NullEntity, BallAndStickEntity, DistanceRestraintEntity, CartoonEntity, BackboneEntity } from './entity'; import { Model, Structure, Format } from 'mol-model/structure'; import { StateContext } from './context'; @@ -13,6 +13,8 @@ import StructureSymmetry from 'mol-model/structure/structure/symmetry'; import { SpacefillProps, SpacefillRepresentation } from 'mol-geo/representation/structure/spacefill'; import { BallAndStickProps, BallAndStickRepresentation } from 'mol-geo/representation/structure/ball-and-stick'; import { DistanceRestraintRepresentation, DistanceRestraintProps } from 'mol-geo/representation/structure/distance-restraint'; +import { CartoonRepresentation, CartoonProps } from 'mol-geo/representation/structure/cartoon'; +import { BackboneProps, BackboneRepresentation } from 'mol-geo/representation/structure/backbone'; type transformer<I extends AnyEntity, O extends AnyEntity, P extends {}> = (ctx: StateContext, inputEntity: I, props?: P) => Promise<O> @@ -124,6 +126,27 @@ export const StructureToDistanceRestraint: StructureToDistanceRestraint = StateT console.log('stats', ctx.viewer.stats) return DistanceRestraintEntity.ofRepr(ctx, distanceRestraintRepr) }) +export type StructureToBackbone = StateTransform<StructureEntity, BackboneEntity, BackboneProps> +export const StructureToBackbone: StructureToBackbone = StateTransform.create('structure', 'backbone', 'structure-to-backbone', + async function (ctx: StateContext, structureEntity: StructureEntity, props: BackboneProps = {}) { + const backboneRepr = BackboneRepresentation() + await backboneRepr.create(structureEntity.value, props).run(ctx.log) + ctx.viewer.add(backboneRepr) + ctx.viewer.requestDraw() + console.log('stats', ctx.viewer.stats) + return BackboneEntity.ofRepr(ctx, backboneRepr) + }) + +export type StructureToCartoon = StateTransform<StructureEntity, CartoonEntity, CartoonProps> +export const StructureToCartoon: StructureToCartoon = StateTransform.create('structure', 'cartoon', 'structure-to-cartoon', + async function (ctx: StateContext, structureEntity: StructureEntity, props: CartoonProps = {}) { + const cartoonRepr = CartoonRepresentation() + await cartoonRepr.create(structureEntity.value, props).run(ctx.log) + ctx.viewer.add(cartoonRepr) + ctx.viewer.requestDraw() + console.log('stats', ctx.viewer.stats) + return CartoonEntity.ofRepr(ctx, cartoonRepr) + }) export type SpacefillUpdate = StateTransform<SpacefillEntity, NullEntity, SpacefillProps> export const SpacefillUpdate: SpacefillUpdate = StateTransform.create('spacefill', 'null', 'spacefill-update', @@ -158,6 +181,28 @@ export const DistanceRestraintUpdate: DistanceRestraintUpdate = StateTransform.c return NullEntity }) +export type BackboneUpdate = StateTransform<BackboneEntity, NullEntity, BackboneProps> +export const BackboneUpdate: BackboneUpdate = StateTransform.create('backbone', 'null', 'backbone-update', + async function (ctx: StateContext, backboneEntity: BackboneEntity, props: BackboneProps = {}) { + const backboneRepr = backboneEntity.value + await backboneRepr.update(props).run(ctx.log) + ctx.viewer.add(backboneRepr) + ctx.viewer.requestDraw() + console.log('stats', ctx.viewer.stats) + return NullEntity + }) + +export type CartoonUpdate = StateTransform<CartoonEntity, NullEntity, CartoonProps> +export const CartoonUpdate: CartoonUpdate = StateTransform.create('cartoon', 'null', 'cartoon-update', + async function (ctx: StateContext, cartoonEntity: CartoonEntity, props: CartoonProps = {}) { + const cartoonRepr = cartoonEntity.value + await cartoonRepr.update(props).run(ctx.log) + ctx.viewer.add(cartoonRepr) + ctx.viewer.requestDraw() + console.log('stats', ctx.viewer.stats) + return NullEntity + }) + // composed export type MmcifUrlToModel = StateTransform<UrlEntity, ModelEntity, {}>