Compare commits

...

56 Commits

Author SHA1 Message Date
Alexander Rose
112cfcac90 0.6.0-dev.6 2020-03-20 14:21:38 -07:00
David Sehnal
8016fcbd54 mol-plugin-state: use 'deposited' as default value for structure if no assembly is present 2020-03-20 21:37:23 +01:00
David Sehnal
6de97f1d03 Merge branch 'master' of https://github.com/molstar/molstar 2020-03-20 21:22:54 +01:00
David Sehnal
204075bbe0 updateStructureComponent bugfix 2020-03-20 21:22:40 +01:00
David Sehnal
eb448bce37 fix volume streaming with custom props enabled 2020-03-20 20:59:23 +01:00
Alexander Rose
3d09b5cb67 use shallow cloning in PD.merge 2020-03-20 12:53:28 -07:00
Alexander Rose
35d06040f7 update debugHelper when visibility changes 2020-03-20 12:22:07 -07:00
Alexander Rose
0868e81944 improved download density action
- combined pdb providers
2020-03-20 12:08:44 -07:00
Alexander Rose
7efbeb7d0f improved download structure action
- updated pdb-dev url
- added archival pdbe
- combined pdb providers
2020-03-20 11:12:52 -07:00
David Sehnal
815f61b550 StructureHierarchyManagerState: auto-select newly added refs 2020-03-20 16:17:47 +01:00
David Sehnal
c3a90ab499 mol-canvas3d: shouldResetCamera check 2020-03-20 13:35:20 +01:00
David Sehnal
321126afa2 show current selection desc in StructureComponentControls header 2020-03-20 11:52:34 +01:00
Alexander Rose
9ad4a9c3c9 recreate vao to fix attempted binding of deleted attributes 2020-03-19 17:29:04 -07:00
Alexander Rose
b12f7c7ce4 add .name property to ShaderCode 2020-03-19 17:26:43 -07:00
Alexander Rose
6d7d3c0794 unitcell label tweaks 2020-03-19 14:24:32 -07:00
Alexander Rose
92b988a8d5 added PD.merge, always use props from structure.root for custom properties 2020-03-19 14:07:48 -07:00
Alexander Rose
18952ee2bd deepClone fix 2020-03-19 14:03:39 -07:00
David Sehnal
bff07888f9 StructureBuilder.tryCreateComponent* refactoring 2020-03-19 20:32:50 +01:00
David Sehnal
8e6b0b220a do not auto-attach interactions in component manager 2020-03-19 20:12:29 +01:00
David Sehnal
74acb0d078 mol-model-state: fix custom props transform bug 2020-03-19 19:14:11 +01:00
Alexander Rose
d4727eea02 removed console.log statement 2020-03-19 10:07:48 -07:00
Alexander Rose
76d14eba0c fix broken units-representation visual state update 2020-03-19 10:01:57 -07:00
David Sehnal
90d05d9260 fix bug in structure repr interaction 2020-03-19 17:43:11 +01:00
David Sehnal
23b24bbb6c custom props are now included by default
+ structure parent helper now takes decorators into account
+ ui & api tweaks
2020-03-19 15:33:37 +01:00
David Sehnal
91bc6f07c5 mol-plugin-state: added TrajectoryHierarchy presets
refactored representation presets
2020-03-19 12:56:47 +01:00
David Sehnal
9b2181667d mol-plugin-state: do not create empty unit cells 2020-03-19 10:59:35 +01:00
Alexander Rose
40347e3e46 0.6.0-dev.5 2020-03-18 21:16:44 -07:00
Alexander Rose
ed277f6e16 Merge remote-tracking branch 'origin/master' into objects 2020-03-18 21:12:52 -07:00
Alexander Rose
8f2085d8b4 0.6.0-dev.4 2020-03-18 20:59:42 -07:00
Alexander Rose
b5a48ad201 package updates 2020-03-18 20:59:01 -07:00
Alexander Rose
14b900791f wip, unitcell ref 2020-03-18 20:49:30 -07:00
Alexander Rose
de80799e68 allow any Loci in CameraManager.focusLoci 2020-03-18 20:44:54 -07:00
Alexander Rose
53eca387fc take renderable visibility into account for scene bounding-sphere 2020-03-18 20:35:14 -07:00
Alexander Rose
fc3005f271 ui tweaks 2020-03-18 16:47:57 -07:00
Alexander Rose
c95fdff00c tweaked StructureSelectionCategorys 2020-03-18 16:04:09 -07:00
Alexander Rose
3cd9042c72 added helpers to Model, better InitVolumeStreaming.isApplicable check 2020-03-18 15:47:58 -07:00
Alexander Rose
4d3914426e improved LociLabelManager to handle multiple loci 2020-03-18 15:46:29 -07:00
David Sehnal
af1fb7041e removed apps/model-server-query 2020-03-18 18:28:19 +01:00
David Sehnal
e2eb1bf223 model-server: config tweak 2020-03-18 13:49:54 +01:00
David Sehnal
5a64a6f1a3 mol-model: fixed a bug in secondary structure export 2020-03-18 13:45:43 +01:00
David Sehnal
88aa9303d7 model-server: fixed data_source bug, allow fetching source data over http(s) 2020-03-18 13:20:08 +01:00
Alexander Rose
5c77eec184 bounding-sphere fixes
- fixed renderable.spec
- simplify extrema for calculateInvariantBoundingSphere when possible
- fixed Sphere3D.expand
2020-03-17 23:19:26 -07:00
Alexander Rose
a931ed7c01 Merge branch 'master' into objects 2020-03-17 21:46:22 -07:00
Alexander Rose
94cd2b618c wip 2020-03-17 20:17:41 -07:00
Alexander Rose
9c97fc258d better box3d calculation in boundary-helper 2020-03-17 16:35:57 -07:00
Alexander Rose
0ac1cfe555 improved bounding sphere- drop hierarchy in favor of extrema points 2020-03-17 16:28:34 -07:00
Alexander Rose
3942f1bc33 added PickRequired type helper 2020-03-17 15:21:06 -07:00
Alexander Rose
a66e38a901 use unit/structure bounding-sphere in visuals geometry 2020-03-17 12:15:39 -07:00
Alexander Rose
f65f4f4aeb seperate grid and boundary calculation 2020-03-17 12:14:36 -07:00
Alexander Rose
f28b13bf87 shader: fixed broken flat-shaded & flip-sided 2020-03-17 09:48:28 -07:00
David Sehnal
8f211a0785 servers readme tweak 2020-03-17 15:50:05 +01:00
David Sehnal
ba1dfb2851 remove preprocess app from webpack config 2020-03-17 15:35:06 +01:00
Alexander Rose
7ccf36a0fa moved unitcell representation to mol-repr/shape 2020-03-16 22:38:46 -07:00
Alexander Rose
b0ee640c12 calc bespoke boundingSphere for unitcell 2020-03-16 22:18:09 -07:00
Alexander Rose
cd6d41a035 allow direct setting of geo bounding sphere
- support mesh, lines, text, spheres, points
2020-03-16 21:20:07 -07:00
Alexander Rose
9c8f1f3e11 wip 2020-03-16 17:20:20 -07:00
126 changed files with 2355 additions and 1937 deletions

View File

@@ -30,7 +30,7 @@ node lib/servers/model/server/server
## From NPM
```
npm install molstar
npm install --production molstar
./model-server
```

View File

@@ -34,7 +34,7 @@ node lib/servers/volume/server
## From NPM
```
npm install molstar
npm install --production molstar
./volume-server
```

1011
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "0.6.0-dev.3",
"version": "0.6.0-dev.6",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -74,16 +74,16 @@
],
"license": "MIT",
"devDependencies": {
"@graphql-codegen/add": "^1.13.0",
"@graphql-codegen/cli": "^1.13.0",
"@graphql-codegen/time": "^1.13.0",
"@graphql-codegen/typescript": "^1.13.0",
"@graphql-codegen/typescript-graphql-files-modules": "^1.13.0",
"@graphql-codegen/typescript-graphql-request": "^1.13.0",
"@graphql-codegen/typescript-operations": "^1.13.0",
"@graphql-codegen/add": "^1.13.1",
"@graphql-codegen/cli": "^1.13.1",
"@graphql-codegen/time": "^1.13.1",
"@graphql-codegen/typescript": "^1.13.1",
"@graphql-codegen/typescript-graphql-files-modules": "^1.13.1",
"@graphql-codegen/typescript-graphql-request": "^1.13.1",
"@graphql-codegen/typescript-operations": "^1.13.1",
"@types/cors": "^2.8.6",
"@typescript-eslint/eslint-plugin": "^2.23.0",
"@typescript-eslint/parser": "^2.23.0",
"@typescript-eslint/eslint-plugin": "^2.24.0",
"@typescript-eslint/parser": "^2.24.0",
"benchmark": "^2.1.4",
"circular-dependency-plugin": "^5.2.0",
"concurrently": "^5.1.0",
@@ -91,7 +91,7 @@
"css-loader": "^3.4.2",
"eslint": "^6.8.0",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^5.1.0",
"file-loader": "^6.0.0",
"fs-extra": "^8.1.0",
"http-server": "^0.12.1",
"jest": "^25.1.0",
@@ -115,7 +115,7 @@
"@types/compression": "1.7.0",
"@types/express": "^4.17.3",
"@types/jest": "^25.1.4",
"@types/node": "^13.9.0",
"@types/node": "^13.9.2",
"@types/node-fetch": "^2.5.5",
"@types/react": "^16.9.23",
"@types/react-dom": "^16.9.5",
@@ -126,7 +126,7 @@
"cors": "^2.8.5",
"express": "^4.17.1",
"graphql": "^14.6.0",
"immer": "^6.0.1",
"immer": "^6.0.2",
"immutable": "^3.8.2",
"node-fetch": "^2.6.0",
"react": "^16.13.0",

View File

@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>Mol* ModelServer Query Builder</title>
</head>
<body>
<div id="app"></div>
<script type="text/javascript" src="./index.js"></script>
</body>
</html>

View File

@@ -1,134 +0,0 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import * as Rx from 'rxjs'
import { QueryDefinition, QueryList } from '../../servers/model/server/api'
import './index.html'
interface State {
query: Rx.BehaviorSubject<QueryDefinition>,
id: Rx.BehaviorSubject<string>,
params: Rx.BehaviorSubject<any>,
isBinary: Rx.BehaviorSubject<boolean>,
models: Rx.BehaviorSubject<number[]>,
url: Rx.Subject<string>
}
class Root extends React.Component<{ state: State }, { }> {
render() {
return <div>
<div>
Query: <QuerySelect state={this.props.state} />
</div>
<div>
ID: <input type='text' onChange={t => this.props.state.id.next(t.currentTarget.value)} />
</div>
<div>
Params:<br/>
<QueryParams state={this.props.state} />
</div>
<div>
Model numbers (empty for all): <ModelNums state={this.props.state} />
</div>
<div>
<input type='checkbox' onChange={t => this.props.state.isBinary.next(!!t.currentTarget.checked)} /> Binary
</div>
<div>
Query string:
<QueryUrl state={this.props.state} />
</div>
</div>
}
}
class QuerySelect extends React.Component<{ state: State }> {
render() {
return <select onChange={s => this.props.state.query.next(QueryList[+s.currentTarget.value].definition)}>
{ QueryList.map((q, i) => <option value={i} key={i} selected={i === 1}>{q.definition.niceName}</option>) }
</select>
}
}
class QueryParams extends React.Component<{ state: State }, { prms: string }> {
state = { prms: '' };
parseParams(str: string) {
this.setState({ prms: str });
try {
const params = JSON.parse(str);
this.props.state.params.next(params);
} catch {
this.props.state.params.next({});
}
}
componentDidMount() {
this.props.state.query.subscribe(q => this.setState({ prms: formatParams(q) }))
}
render() {
return <textarea style={{height: '300px'}} value={this.state.prms} cols={80} onChange={t => this.parseParams(t.currentTarget.value)} />;
}
}
class QueryUrl extends React.Component<{ state: State }, { queryString: string }> {
state = { queryString: '' };
componentDidMount() {
this.props.state.url.subscribe(url => this.setState({ queryString: url }))
}
render() {
return <input type='text' value={this.state.queryString} style={{ width: '800px' }} />
}
}
class ModelNums extends React.Component<{ state: State }> {
render() {
return <input type='text' defaultValue='1' style={{ width: '300px' }} onChange={t =>
this.props.state.models.next(t.currentTarget.value.split(',')
.map(v => v.trim())
.filter(v => !!v)
.map(v => +v)
)} />
}
}
const state: State = {
query: new Rx.BehaviorSubject(QueryList[1].definition),
id: new Rx.BehaviorSubject('1cbs'),
params: new Rx.BehaviorSubject({ }),
isBinary: new Rx.BehaviorSubject<boolean>(false),
models: new Rx.BehaviorSubject<number[]>([]),
url: new Rx.Subject()
}
function formatParams(def: QueryDefinition) {
const prms = Object.create(null);
for (const p of def.jsonParams) {
prms[p.name] = p.exampleValues ? p.exampleValues[0] : void 0;
}
return JSON.stringify(prms, void 0, 2);
}
function formatUrl() {
const json = JSON.stringify({
name: state.query.value.name,
id: state.id.value,
modelNums: state.models.value.length ? state.models.value : void 0,
binary: state.isBinary.value,
params: state.params.value
});
state.url.next(encodeURIComponent(json));
}
Rx.merge(state.query, state.id, state.params, state.isBinary, state.models).subscribe(s => formatUrl());
ReactDOM.render(<Root state={state} />, document.getElementById('app'));

View File

@@ -88,7 +88,6 @@ async function tryLoadFromUrl(ctx: PluginContext) {
format: format as any,
isBinary,
options: params.source.params.options,
structure: params.source.params.structure,
}
}
}));

View File

@@ -74,6 +74,7 @@ interface Canvas3D {
commit(isSynchronous?: boolean): void
update(repr?: Representation.Any, keepBoundingSphere?: boolean): void
clear(): void
syncVisibility(): void
requestDraw(force?: boolean): void
animate(): void
@@ -312,16 +313,38 @@ namespace Canvas3D {
cameraResetRequested = false;
}
const oldBoundary = Sphere3D.zero();
function shouldResetCamera() {
if (camera.state.radiusMax === 0) return true;
// check if any renderable center has moved outside of the old boundary
for (const r of scene.renderables) {
if (!r.state.visible) continue;
const { center, radius } = r.values.boundingSphere.ref.value;
if (!radius) continue;
// TODO: include renderable radius into this?
if (Vec3.distance(oldBoundary.center, center) > 1.1 * oldBoundary.radius) {
return true;
}
}
return false;
}
const sceneCommitTimeoutMs = 250;
function commitScene(isSynchronous: boolean) {
if (!scene.needsCommit) return true;
// snapshot the current bounding sphere
Sphere3D.copy(oldBoundary, scene.boundingSphere);
if (!scene.commit(isSynchronous ? void 0 : sceneCommitTimeoutMs)) return false;
if (debugHelper.isEnabled) debugHelper.update();
if (reprCount.value === 0 || camera.state.radiusMax === 0) cameraResetRequested = true;
if (reprCount.value === 0 || shouldResetCamera()) cameraResetRequested = true;
camera.setState({ radiusMax: scene.boundingSphere.radius })
reprCount.next(reprRenderObjects.size);
return true;
}
@@ -397,6 +420,12 @@ namespace Canvas3D {
requestDraw(true)
reprCount.next(reprRenderObjects.size)
},
syncVisibility: () => {
if (scene.syncVisibility()) {
camera.setState({ radiusMax: scene.boundingSphere.radius })
if (debugHelper.isEnabled) debugHelper.update()
}
},
// draw,
requestDraw,

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -45,16 +45,16 @@ export class BoundingSphereHelper {
}
update() {
const newSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphere, this.sceneData, ColorNames.grey)
const newSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphere, this.sceneData, ColorNames.grey, sceneMaterialId)
if (newSceneData) this.sceneData = newSceneData
this.parent.forEach((r, ro) => {
const objectData = this.objectsData.get(ro)
const newObjectData = updateBoundingSphereData(this.scene, r.values.boundingSphere.ref.value, objectData, ColorNames.tomato)
const newObjectData = updateBoundingSphereData(this.scene, r.values.boundingSphere.ref.value, objectData, ColorNames.tomato, objectMaterialId)
if (newObjectData) this.objectsData.set(ro, newObjectData)
const instanceData = this.instancesData.get(ro)
const newInstanceData = updateBoundingSphereData(this.scene, r.values.invariantBoundingSphere.ref.value, instanceData, ColorNames.skyblue, {
const newInstanceData = updateBoundingSphereData(this.scene, r.values.invariantBoundingSphere.ref.value, instanceData, ColorNames.skyblue, instanceMaterialId, {
aTransform: ro.values.aTransform,
matrix: ro.values.matrix,
transform: ro.values.transform,
@@ -114,10 +114,10 @@ export class BoundingSphereHelper {
}
}
function updateBoundingSphereData(scene: Scene, boundingSphere: Sphere3D, data: BoundingSphereData | undefined, color: Color, transform?: TransformData) {
function updateBoundingSphereData(scene: Scene, boundingSphere: Sphere3D, data: BoundingSphereData | undefined, color: Color, materialId: number, transform?: TransformData) {
if (!data || !Sphere3D.equals(data.boundingSphere, boundingSphere)) {
const mesh = createBoundingSphereMesh(boundingSphere, data && data.mesh)
const renderObject = data ? data.renderObject : createBoundingSphereRenderObject(mesh, color, transform)
const renderObject = data ? data.renderObject : createBoundingSphereRenderObject(mesh, color, materialId, transform)
if (data) {
ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(mesh))
} else {
@@ -132,15 +132,19 @@ function createBoundingSphereMesh(boundingSphere: Sphere3D, mesh?: Mesh) {
const vertexCount = sphereVertexCount(detail)
const builderState = MeshBuilder.createState(vertexCount, vertexCount / 2, mesh)
if (boundingSphere.radius) {
for (const b of Sphere3D.getList(boundingSphere)) {
addSphere(builderState, b.center, b.radius, detail)
addSphere(builderState, boundingSphere.center, boundingSphere.radius, detail)
if (Sphere3D.hasExtrema(boundingSphere)) {
for (const e of boundingSphere.extrema) addSphere(builderState, e, 1.0, 0)
}
}
return MeshBuilder.getMesh(builderState)
}
const boundingSphereHelberMaterialId = getNextMaterialId()
function createBoundingSphereRenderObject(mesh: Mesh, color: Color, transform?: TransformData) {
const sceneMaterialId = getNextMaterialId()
const objectMaterialId = getNextMaterialId()
const instanceMaterialId = getNextMaterialId()
function createBoundingSphereRenderObject(mesh: Mesh, color: Color, materialId: number, transform?: TransformData) {
const values = Mesh.Utils.createValuesSimple(mesh, { alpha: 0.1, doubleSided: false }, color, 1, transform)
return createRenderObject('mesh', values, { visible: true, alphaFactor: 1, pickable: false, opaque: false }, boundingSphereHelberMaterialId)
return createRenderObject('mesh', values, { visible: true, alphaFactor: 1, pickable: false, opaque: false }, materialId)
}

View File

@@ -40,7 +40,7 @@ function getComposeRenderable(ctx: WebGLContext, colorTexture: Texture): Compose
}
const schema = { ...ComposeSchema }
const shaderCode = ShaderCode(quad_vert, compose_frag)
const shaderCode = ShaderCode('compose', quad_vert, compose_frag)
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
return createComputeRenderable(renderItem, values)

View File

@@ -96,7 +96,7 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d
}
const schema = { ...PostprocessingSchema }
const shaderCode = ShaderCode(quad_vert, postprocessing_frag)
const shaderCode = ShaderCode('postprocessing', quad_vert, postprocessing_frag)
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
return createComputeRenderable(renderItem, values)

View File

@@ -6,9 +6,7 @@
import { ValueCell } from '../../../mol-util'
import { Mat4 } from '../../../mol-math/linear-algebra'
import { transformPositionArray,/* , transformDirectionArray, getNormalMatrix */
GroupMapping,
createGroupMapping} from '../../util';
import { transformPositionArray, GroupMapping, createGroupMapping} from '../../util';
import { GeometryUtils } from '../geometry';
import { createColors } from '../color-data';
import { createMarkers } from '../marker-data';
@@ -50,6 +48,8 @@ export interface Lines {
readonly boundingSphere: Sphere3D
/** Maps group ids to line indices */
readonly groupMapping: GroupMapping
setBoundingSphere(boundingSphere: Sphere3D): void
}
export namespace Lines {
@@ -129,6 +129,10 @@ export namespace Lines {
currentGroup = lines.groupBuffer.ref.version
}
return groupMapping
},
setBoundingSphere(sphere: Sphere3D) {
Sphere3D.copy(boundingSphere, sphere)
currentHash = hashCode(lines)
}
}
return lines

View File

@@ -45,6 +45,8 @@ export interface Mesh {
readonly boundingSphere: Sphere3D
/** Maps group ids to vertex indices */
readonly groupMapping: GroupMapping
setBoundingSphere(boundingSphere: Sphere3D): void
}
export namespace Mesh {
@@ -101,6 +103,10 @@ export namespace Mesh {
currentGroup = mesh.groupBuffer.ref.version
}
return groupMapping
},
setBoundingSphere(sphere: Sphere3D) {
Sphere3D.copy(boundingSphere, sphere)
currentHash = hashCode(mesh)
}
}
return mesh

View File

@@ -6,9 +6,7 @@
import { ValueCell } from '../../../mol-util'
import { Mat4 } from '../../../mol-math/linear-algebra'
import { transformPositionArray,/* , transformDirectionArray, getNormalMatrix */
GroupMapping,
createGroupMapping} from '../../util';
import { transformPositionArray, GroupMapping, createGroupMapping} from '../../util';
import { GeometryUtils } from '../geometry';
import { createColors } from '../color-data';
import { createMarkers } from '../marker-data';
@@ -43,6 +41,8 @@ export interface Points {
readonly boundingSphere: Sphere3D
/** Maps group ids to point indices */
readonly groupMapping: GroupMapping
setBoundingSphere(boundingSphere: Sphere3D): void
}
export namespace Points {
@@ -92,6 +92,10 @@ export namespace Points {
currentGroup = points.groupBuffer.ref.version
}
return groupMapping
},
setBoundingSphere(sphere: Sphere3D) {
Sphere3D.copy(boundingSphere, sphere)
currentHash = hashCode(points)
}
}
return points

View File

@@ -42,6 +42,8 @@ export interface Spheres {
readonly boundingSphere: Sphere3D
/** Maps group ids to sphere indices */
readonly groupMapping: GroupMapping
setBoundingSphere(boundingSphere: Sphere3D): void
}
export namespace Spheres {
@@ -97,6 +99,10 @@ export namespace Spheres {
currentGroup = spheres.groupBuffer.ref.version
}
return groupMapping
},
setBoundingSphere(sphere: Sphere3D) {
Sphere3D.copy(boundingSphere, sphere)
currentHash = hashCode(spheres)
}
}
return spheres

View File

@@ -62,6 +62,8 @@ export interface Text {
readonly boundingSphere: Sphere3D
/** Maps group ids to text indices */
readonly groupMapping: GroupMapping
setBoundingSphere(boundingSphere: Sphere3D): void
}
export namespace Text {
@@ -124,6 +126,10 @@ export namespace Text {
currentGroup = text.groupBuffer.ref.version
}
return groupMapping
},
setBoundingSphere(sphere: Sphere3D) {
Sphere3D.copy(boundingSphere, sphere)
currentHash = hashCode(text)
}
}
return text

View File

@@ -1,42 +1,44 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
// import { calculateBoundingSphere } from '../renderable/util';
// import { Vec3 } from '../../mol-math/linear-algebra';
import { calculateBoundingSphere } from '../renderable/util';
describe('renderable', () => {
it('calculateBoundingSphere', () => {
// const position = new Float32Array([
// 0, 0, 0,
// 1, 0, 0
// ])
// const transform = new Float32Array([
// 1, 0, 0, 0,
// 0, 1, 0, 0,
// 0, 0, 1, 0,
// 0, 0, 0, 0,
const position = new Float32Array([
0, 0, 0,
1, 0, 0,
-1, 0, 0,
])
const transform = new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 0,
// 1, 0, 0, 0,
// 0, 1, 0, 0,
// 0, 0, 1, 0,
// 1, 0, 0, 0,
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
1, 0, 0, 0,
// 1, 0, 0, 0,
// 0, 1, 0, 0,
// 0, 0, 1, 0,
// 2, 0, 0, 0
// ])
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
-1, 0, 0, 0
])
// const { boundingSphere } = calculateBoundingSphere(
// position, position.length / 3,
// transform, transform.length / 16
// )
const { boundingSphere, invariantBoundingSphere } = calculateBoundingSphere(
position, position.length / 3,
transform, transform.length / 16
)
// TODO:
// expect(boundingSphere.radius).toBeCloseTo(1.58, 2)
// expect(Vec3.equals(boundingSphere.center, Vec3.create(1.418367, 0, 0))).toBe(true)
expect(invariantBoundingSphere.extrema).toEqual([[0, 0, 0], [1, 0, 0], [-1, 0, 0]])
expect(invariantBoundingSphere.radius).toBe(1)
expect(invariantBoundingSphere.center).toEqual([0, 0, 0])
expect(boundingSphere.radius).toBe(2)
expect(boundingSphere.center).toEqual([0, 0, 0])
})
})

View File

@@ -41,7 +41,7 @@ function getHistopyramidReductionRenderable(ctx: WebGLContext, initialTexture: T
}
const schema = { ...HistopyramidReductionSchema }
const shaderCode = ShaderCode(quad_vert, reduction_frag)
const shaderCode = ShaderCode('reduction', quad_vert, reduction_frag)
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
HistopyramidReductionRenderable = createComputeRenderable(renderItem, values);

View File

@@ -34,7 +34,7 @@ function getHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture) {
}
const schema = { ...HistopyramidSumSchema }
const shaderCode = ShaderCode(quad_vert, sum_frag)
const shaderCode = ShaderCode('sum', quad_vert, sum_frag)
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
HistopyramidSumRenderable = createComputeRenderable(renderItem, values)

View File

@@ -46,7 +46,7 @@ function getActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridD
}
const schema = { ...ActiveVoxelsSchema }
const shaderCode = ShaderCode(quad_vert, active_voxels_frag)
const shaderCode = ShaderCode('active-voxels', quad_vert, active_voxels_frag)
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
return createComputeRenderable(renderItem, values);

View File

@@ -62,7 +62,7 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
}
const schema = { ...IsosurfaceSchema }
const shaderCode = ShaderCode(quad_vert, isosurface_frag, { drawBuffers: true })
const shaderCode = ShaderCode('isosurface', quad_vert, isosurface_frag, { drawBuffers: true })
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
return createComputeRenderable(renderItem, values);

View File

@@ -6,7 +6,7 @@
import { Sphere3D } from '../../mol-math/geometry'
import { Vec3 } from '../../mol-math/linear-algebra'
import { BoundaryHelper, HierarchyHelper } from '../../mol-math/geometry/boundary-helper';
import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
export function calculateTextureInfo (n: number, itemSize: number) {
const sqN = Math.sqrt(n)
@@ -83,22 +83,14 @@ export function printImageData(imageData: ImageData, scale = 1, pixelated = fals
const v = Vec3.zero()
const boundaryHelperCoarse = new BoundaryHelper('14')
const boundaryHelperFine = new BoundaryHelper('98')
const hierarchyHelperCoarse = new HierarchyHelper('14')
const hierarchyHelperFine = new HierarchyHelper('98')
function getHelper(count: number) {
return count > 500_000 ? {
boundaryHelper: boundaryHelperCoarse,
hierarchyHelper: hierarchyHelperCoarse
} : {
boundaryHelper: boundaryHelperFine,
hierarchyHelper: hierarchyHelperFine
}
return count > 500_000 ? boundaryHelperCoarse : boundaryHelperFine
}
export function calculateInvariantBoundingSphere(position: Float32Array, positionCount: number, stepFactor: number): Sphere3D {
const step = stepFactor * 3
const { boundaryHelper, hierarchyHelper } = getHelper(positionCount)
const boundaryHelper = getHelper(positionCount)
boundaryHelper.reset()
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
@@ -111,49 +103,52 @@ export function calculateInvariantBoundingSphere(position: Float32Array, positio
boundaryHelper.radiusStep(v)
}
const hierarchyInput = boundaryHelper.getHierarchyInput()
if (hierarchyInput) {
hierarchyHelper.reset(hierarchyInput.sphere, hierarchyInput.normal)
const sphere = boundaryHelper.getSphere()
if (positionCount <= 98) {
const extrema: Vec3[] = []
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
Vec3.fromArray(v, position, i)
hierarchyHelper.includeStep(v)
extrema.push(Vec3.fromArray(Vec3(), position, i));
}
hierarchyHelper.finishedIncludeStep()
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
Vec3.fromArray(v, position, i)
hierarchyHelper.radiusStep(v)
}
return hierarchyHelper.getSphere()
} else {
return boundaryHelper.getSphere()
Sphere3D.setExtrema(sphere, extrema)
}
return sphere
}
export function calculateTransformBoundingSphere(invariantBoundingSphere: Sphere3D, transform: Float32Array, transformCount: number): Sphere3D {
const { boundaryHelper } = getHelper(transformCount)
const boundaryHelper = getHelper(transformCount)
boundaryHelper.reset()
const transformedSpheres: Sphere3D[] = []
for (const b of Sphere3D.getList(invariantBoundingSphere)) {
const { center, radius, extrema } = invariantBoundingSphere
if (extrema) {
for (let i = 0, _i = transformCount; i < _i; ++i) {
const c = Vec3.transformMat4Offset(Vec3(), b.center, transform, 0, 0, i * 16)
transformedSpheres.push(Sphere3D.create(c as Vec3, b.radius))
for (const e of extrema) {
Vec3.transformMat4Offset(v, e, transform, 0, 0, i * 16)
boundaryHelper.includeStep(v)
}
}
boundaryHelper.finishedIncludeStep()
for (let i = 0, _i = transformCount; i < _i; ++i) {
for (const e of extrema) {
Vec3.transformMat4Offset(v, e, transform, 0, 0, i * 16)
boundaryHelper.radiusStep(v)
}
}
} else {
for (let i = 0, _i = transformCount; i < _i; ++i) {
Vec3.transformMat4Offset(v, center, transform, 0, 0, i * 16)
boundaryHelper.includeSphereStep(v, radius)
}
boundaryHelper.finishedIncludeStep()
for (let i = 0, _i = transformCount; i < _i; ++i) {
Vec3.transformMat4Offset(v, center, transform, 0, 0, i * 16)
boundaryHelper.radiusSphereStep(v, radius)
}
}
for (const b of transformedSpheres) {
boundaryHelper.includeSphereStep(b.center, b.radius)
}
boundaryHelper.finishedIncludeStep()
for (const b of transformedSpheres) {
boundaryHelper.radiusSphereStep(b.center, b.radius)
}
const sphere = boundaryHelper.getSphere()
if (transformedSpheres.length > 1) {
(sphere as Sphere3D.Hierarchy).hierarchy = transformedSpheres
}
return sphere
return boundaryHelper.getSphere()
}
export function calculateBoundingSphere(position: Float32Array, positionCount: number, transform: Float32Array, transformCount: number, padding = 0, stepFactor = 1): { boundingSphere: Sphere3D, invariantBoundingSphere: Sphere3D } {

View File

@@ -16,7 +16,6 @@ import { ValueCell } from '../mol-util';
import { RenderableValues, GlobalUniformValues, BaseValues } from './renderable/schema';
import { GraphicsRenderVariant } from './webgl/render-item';
import { ParamDefinition as PD } from '../mol-util/param-definition';
import { deepClone } from '../mol-util/object';
export interface RendererStats {
programCount: number
@@ -110,7 +109,7 @@ function getStyle(props: RendererProps['style']) {
namespace Renderer {
export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
const { gl, state, stats } = ctx
const p = deepClone({ ...PD.getDefaultValues(RendererParams), ...props })
const p = PD.merge(RendererParams, PD.getDefaultValues(RendererParams), props)
const style = getStyle(p.style)
const viewport = Viewport()

View File

@@ -15,6 +15,7 @@ import { CommitQueue } from './commit-queue';
import { now } from '../mol-util/now';
import { arraySetRemove } from '../mol-util/array';
import { BoundaryHelper } from '../mol-math/geometry/boundary-helper';
import { hash1 } from '../mol-data/util';
const boundaryHelper = new BoundaryHelper('98')
@@ -22,20 +23,32 @@ function calculateBoundingSphere(renderables: Renderable<RenderableValues & Base
boundaryHelper.reset();
for (let i = 0, il = renderables.length; i < il; ++i) {
if (!renderables[i].state.visible) continue;
const boundingSphere = renderables[i].values.boundingSphere.ref.value
if (!boundingSphere.radius) continue;
for (const b of Sphere3D.getList(boundingSphere)) {
boundaryHelper.includeSphereStep(b.center, b.radius);
if (Sphere3D.hasExtrema(boundingSphere)) {
for (const e of boundingSphere.extrema) {
boundaryHelper.includeStep(e)
}
} else {
boundaryHelper.includeSphereStep(boundingSphere.center, boundingSphere.radius);
}
}
boundaryHelper.finishedIncludeStep();
for (let i = 0, il = renderables.length; i < il; ++i) {
if (!renderables[i].state.visible) continue;
const boundingSphere = renderables[i].values.boundingSphere.ref.value
if (!boundingSphere.radius) continue;
for (const b of Sphere3D.getList(boundingSphere)) {
boundaryHelper.radiusSphereStep(b.center, b.radius);
if (Sphere3D.hasExtrema(boundingSphere)) {
for (const e of boundingSphere.extrema) {
boundaryHelper.radiusStep(e)
}
} else {
boundaryHelper.radiusSphereStep(boundingSphere.center, boundingSphere.radius);
}
}
@@ -62,6 +75,8 @@ interface Scene extends Object3D {
readonly renderables: ReadonlyArray<Renderable<RenderableValues & BaseValues>>
readonly boundingSphere: Sphere3D
/** Returns `true` if some visibility has changed, `false` otherwise. */
syncVisibility: () => boolean
update: (objects: ArrayLike<GraphicsRenderObject> | undefined, keepBoundingSphere: boolean, isRemoving?: boolean) => void
add: (o: GraphicsRenderObject) => void // Renderable<any>
remove: (o: GraphicsRenderObject) => void
@@ -76,7 +91,7 @@ namespace Scene {
export function create(ctx: WebGLContext): Scene {
const renderableMap = new Map<GraphicsRenderObject, Renderable<RenderableValues & BaseValues>>()
const renderables: Renderable<RenderableValues & BaseValues>[] = []
const boundingSphere = Sphere3D.zero()
const boundingSphere = Sphere3D()
let boundingSphereDirty = true
@@ -131,13 +146,35 @@ namespace Scene {
const commitQueue = new CommitQueue();
let visibleHash = -1
function computeVisibleHash() {
let hash = 23
for (let i = 0, il = renderables.length; i < il; ++i) {
if (!renderables[i].state.visible) continue;
hash = (31 * hash + renderables[i].id) | 0;
}
hash = hash1(hash);
if (hash === -1) hash = 0;
return hash
}
function syncVisibility() {
const newVisibleHash = computeVisibleHash()
if (newVisibleHash !== visibleHash) {
boundingSphereDirty = true
return true
} else {
return false
}
}
return {
get view () { return object3d.view },
get position () { return object3d.position },
get direction () { return object3d.direction },
get up () { return object3d.up },
// get isCommiting () { return commitQueue.length > 0 },
syncVisibility,
update(objects, keepBoundingSphere, isRemoving) {
Object3D.update(object3d)
if (objects) {
@@ -151,7 +188,11 @@ namespace Scene {
renderables[i].update()
}
}
if (!keepBoundingSphere) boundingSphereDirty = true
if (!keepBoundingSphere) {
boundingSphereDirty = true
} else {
syncVisibility()
}
},
add: (o: GraphicsRenderObject) => commitQueue.add(o),
remove: (o: GraphicsRenderObject) => commitQueue.remove(o),
@@ -176,8 +217,11 @@ namespace Scene {
},
renderables,
get boundingSphere() {
if (boundingSphereDirty) calculateBoundingSphere(renderables, boundingSphere)
boundingSphereDirty = false
if (boundingSphereDirty) {
calculateBoundingSphere(renderables, boundingSphere)
boundingSphereDirty = false
visibleHash = computeVisibleHash()
}
return boundingSphere
}
}

View File

@@ -24,6 +24,7 @@ export interface ShaderExtensions {
export interface ShaderCode {
readonly id: number
readonly name: string
readonly vert: string
readonly frag: string
readonly extensions: ShaderExtensions
@@ -37,7 +38,6 @@ import assign_color_varying from './shader/chunks/assign-color-varying.glsl'
import assign_group from './shader/chunks/assign-group.glsl'
import assign_marker_varying from './shader/chunks/assign-marker-varying.glsl'
import assign_material_color from './shader/chunks/assign-material-color.glsl'
import assign_normal from './shader/chunks/assign-normal.glsl'
import assign_position from './shader/chunks/assign-position.glsl'
import assign_size from './shader/chunks/assign-size.glsl'
import check_picking_alpha from './shader/chunks/check-picking-alpha.glsl'
@@ -63,7 +63,6 @@ const ShaderChunks: { [k: string]: string } = {
assign_group,
assign_marker_varying,
assign_material_color,
assign_normal,
assign_position,
assign_size,
check_picking_alpha,
@@ -99,33 +98,33 @@ function addIncludes(text: string) {
.replace(reMultipleLinebreaks, '\n')
}
export function ShaderCode(vert: string, frag: string, extensions: ShaderExtensions = {}): ShaderCode {
return { id: shaderCodeId(), vert: addIncludes(vert), frag: addIncludes(frag), extensions }
export function ShaderCode(name: string, vert: string, frag: string, extensions: ShaderExtensions = {}): ShaderCode {
return { id: shaderCodeId(), name, vert: addIncludes(vert), frag: addIncludes(frag), extensions }
}
import points_vert from './shader/points.vert'
import points_frag from './shader/points.frag'
export const PointsShaderCode = ShaderCode(points_vert, points_frag)
export const PointsShaderCode = ShaderCode('points', points_vert, points_frag)
import spheres_vert from './shader/spheres.vert'
import spheres_frag from './shader/spheres.frag'
export const SpheresShaderCode = ShaderCode(spheres_vert, spheres_frag, { fragDepth: true })
export const SpheresShaderCode = ShaderCode('spheres', spheres_vert, spheres_frag, { fragDepth: true })
import text_vert from './shader/text.vert'
import text_frag from './shader/text.frag'
export const TextShaderCode = ShaderCode(text_vert, text_frag, { standardDerivatives: true })
export const TextShaderCode = ShaderCode('text', text_vert, text_frag, { standardDerivatives: true })
import lines_vert from './shader/lines.vert'
import lines_frag from './shader/lines.frag'
export const LinesShaderCode = ShaderCode(lines_vert, lines_frag)
export const LinesShaderCode = ShaderCode('lines', lines_vert, lines_frag)
import mesh_vert from './shader/mesh.vert'
import mesh_frag from './shader/mesh.frag'
export const MeshShaderCode = ShaderCode(mesh_vert, mesh_frag, { standardDerivatives: true })
export const MeshShaderCode = ShaderCode('mesh', mesh_vert, mesh_frag, { standardDerivatives: true })
import direct_volume_vert from './shader/direct-volume.vert'
import direct_volume_frag from './shader/direct-volume.frag'
export const DirectVolumeShaderCode = ShaderCode(direct_volume_vert, direct_volume_frag, { fragDepth: true })
export const DirectVolumeShaderCode = ShaderCode('direct-volume', direct_volume_vert, direct_volume_frag, { fragDepth: true })
//
@@ -235,6 +234,7 @@ export function addShaderDefines(gl: GLRenderingContext, extensions: WebGLExtens
const frag = isWebGL2(gl) ? transformGlsl300Frag(shaders.frag) : shaders.frag
return {
id: shaderCodeId(),
name: shaders.name,
vert: `${vertPrefix}${header}${shaders.vert}`,
frag: `${fragPrefix}${header}${frag}`,
extensions: shaders.extensions

View File

@@ -1,12 +0,0 @@
export default `
#if defined(dFlatShaded) && defined(enabledStandardDerivatives)
vec3 fdx = dFdx(vViewPosition);
vec3 fdy = dFdy(vViewPosition);
vec3 normal = -normalize(cross(fdx, fdy));
#else
vec3 normal = -normalize(vNormal);
#ifdef dDoubleSided
normal = normal * (float(frontFacing) * 2.0 - 1.0);
#endif
#endif
`

View File

@@ -25,7 +25,11 @@ void main() {
bool frontFacing = dot(vNormal, vViewPosition) < 0.0;
#endif
interior = !frontFacing; // TODO take dFlipSided into account
#if defined(dFlipSided)
interior = frontFacing;
#else
interior = !frontFacing;
#endif
#include assign_material_color
@@ -38,7 +42,14 @@ void main() {
#ifdef dIgnoreLight
gl_FragColor = material;
#else
#include assign_normal
#if defined(dFlatShaded) && defined(enabledStandardDerivatives)
vec3 normal = -faceNormal;
#else
vec3 normal = -normalize(vNormal);
#ifdef dDoubleSided
normal = normal * (float(frontFacing) * 2.0 - 1.0);
#endif
#endif
#include apply_light_color
#endif

View File

@@ -38,10 +38,12 @@ function getLocations(gl: GLRenderingContext, program: WebGLProgram, schema: Ren
const spec = schema[k]
if (spec.type === 'attribute') {
const loc = gl.getAttribLocation(program, k)
// unused attributes will result in a `-1` location which is usually fine
// if (loc === -1) console.info(`Could not get attribute location for '${k}'`)
locations[k] = loc
} else if (spec.type === 'uniform' || spec.type === 'texture') {
const loc = gl.getUniformLocation(program, k)
// unused uniforms will result in a `null` location which is usually fine
// if (loc === null) console.info(`Could not get uniform location for '${k}'`)
locations[k] = loc as number
}
@@ -146,8 +148,8 @@ export function createProgram(gl: GLRenderingContext, state: WebGLState, extensi
const vertShader = getShader('vert', shaderCode.vert)
const fragShader = getShader('frag', shaderCode.frag)
let locations: Locations // = getLocations(gl, program, schema)
let uniformSetters: UniformSetters // = getUniformSetters(schema)
let locations: Locations
let uniformSetters: UniformSetters
function init() {
vertShader.attach(program)
@@ -186,9 +188,9 @@ export function createProgram(gl: GLRenderingContext, state: WebGLState, extensi
}
}
},
bindAttributes: (attribueBuffers: AttributeBuffers) => {
for (let i = 0, il = attribueBuffers.length; i < il; ++i) {
const [k, buffer] = attribueBuffers[i]
bindAttributes: (attributeBuffers: AttributeBuffers) => {
for (let i = 0, il = attributeBuffers.length; i < il; ++i) {
const [k, buffer] = attributeBuffers[i]
const l = locations[k]
if (l !== -1) buffer.bind(l)
}

View File

@@ -198,9 +198,6 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
try {
checkError(ctx.gl)
} catch (e) {
// console.log('shaderCode', shaderCode)
// console.log('schema', schema)
// console.log('attributeBuffers', attributeBuffers)
throw new Error(`Error rendering item id ${id}: '${e}'`)
}
}
@@ -246,10 +243,10 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
const value = attributeValues[k]
if (value.ref.version !== versions[k]) {
if (buffer.length >= value.ref.value.length) {
// console.log('attribute array large enough to update', k, value.ref.id, value.ref.version)
// console.log('attribute array large enough to update', buffer.id, k, value.ref.id, value.ref.version)
buffer.updateData(value.ref.value)
} else {
// console.log('attribute array to small, need to create new attribute', k, value.ref.id, value.ref.version)
// console.log('attribute array too small, need to create new attribute', buffer.id, k, value.ref.id, value.ref.version)
buffer.destroy()
const { itemSize, divisor } = schema[k] as AttributeSpec<AttributeKind>
attributeBuffers[i][1] = resources.attribute(value.ref.value, itemSize, divisor)
@@ -276,7 +273,8 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
// console.log('program/defines or buffers changed, update vaos')
Object.keys(renderVariantDefines).forEach(k => {
const vertexArray = vertexArrays[k]
if (vertexArray) vertexArray.update()
if (vertexArray) vertexArray.destroy()
vertexArrays[k] = vertexArrayObject ? resources.vertexArray(programs[k], attributeBuffers, elementsBuffer) : null
})
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
@@ -7,6 +7,7 @@
import { GridLookup3D } from '../../geometry';
import { sortArray } from '../../../mol-data/util';
import { OrderedSet } from '../../../mol-data/int';
import { getBoundary } from '../boundary';
const xs = [0, 0, 1];
const ys = [0, 1, 0];
@@ -15,7 +16,9 @@ const rs = [0, 0.5, 1/3];
describe('GridLookup3d', () => {
it('basic', () => {
const grid = GridLookup3D({ x: xs, y: ys, z: zs, indices: OrderedSet.ofBounds(0, 3) });
const position = { x: xs, y: ys, z: zs, indices: OrderedSet.ofBounds(0, 3) }
const boundary = getBoundary(position)
const grid = GridLookup3D(position, boundary);
let r = grid.find(0, 0, 0, 0);
expect(r.count).toBe(1);
@@ -27,7 +30,9 @@ describe('GridLookup3d', () => {
});
it('radius', () => {
const grid = GridLookup3D({ x: xs, y: ys, z: zs, radius: [0, 0.5, 1 / 3], indices: OrderedSet.ofBounds(0, 3) });
const position = { x: xs, y: ys, z: zs, radius: [0, 0.5, 1 / 3], indices: OrderedSet.ofBounds(0, 3) }
const boundary = getBoundary(position)
const grid = GridLookup3D(position, boundary);
let r = grid.find(0, 0, 0, 0);
expect(r.count).toBe(1);
@@ -39,7 +44,9 @@ describe('GridLookup3d', () => {
});
it('indexed', () => {
const grid = GridLookup3D({ x: xs, y: ys, z: zs, indices: OrderedSet.ofSingleton(1), radius: rs });
const position = { x: xs, y: ys, z: zs, indices: OrderedSet.ofSingleton(1), radius: rs }
const boundary = getBoundary(position)
const grid = GridLookup3D(position, boundary);
let r = grid.find(0, 0, 0, 0);
expect(r.count).toBe(0);

View File

@@ -11,8 +11,6 @@ import { Box3D } from './primitives/box3d';
// implementing http://www.ep.liu.se/ecp/034/009/ecp083409.pdf
const MinThresholdDist = 0.1
export class BoundaryHelper {
private dir: Vec3[]
@@ -74,36 +72,17 @@ export class BoundaryHelper {
this.centroidHelper.radiusSphereStep(center, radius);
}
getHierarchyInput() {
if (this.centroidHelper.getCount() < 2) return false
const sphere = this.centroidHelper.getSphere();
const normal = Vec3()
const t = sphere.radius * this.hierarchyThresholdFactor
let maxDist = -Infinity
let belowThreshold = false
for (let i = 0; i < this.extrema.length; i += 2) {
const halfDist = Vec3.distance(this.extrema[i], this.extrema[i + 1]) / 2
if (halfDist > maxDist) {
maxDist = halfDist
Vec3.normalize(normal, Vec3.sub(normal, this.extrema[i], this.extrema[i + 1]))
}
if (halfDist < t) belowThreshold = true
}
return (belowThreshold && maxDist > MinThresholdDist) ? { sphere, normal } : false
}
getSphere(sphere?: Sphere3D) {
return this.centroidHelper.getSphere(sphere)
return Sphere3D.setExtrema(this.centroidHelper.getSphere(sphere), this.extrema)
}
getBox(box?: Box3D) {
// TODO can we get a tighter box from the extrema???
if (!box) box = Box3D()
return Box3D.fromSphere3D(box, this.centroidHelper.getSphere())
Box3D.setEmpty(box)
for (let i = 0; i < this.extrema.length; i++) {
Box3D.add(box, this.extrema[i])
}
return box
}
reset() {
@@ -116,69 +95,12 @@ export class BoundaryHelper {
this.centroidHelper.reset()
}
constructor(quality: EposQuality, private hierarchyThresholdFactor = 0.66) {
constructor(quality: EposQuality) {
this.dir = getEposDir(quality)
this.reset()
}
}
export class HierarchyHelper {
private tmpV = Vec3()
private tmpS = Sphere3D()
private sphere = Sphere3D()
private normal = Vec3()
private helperA = new BoundaryHelper(this.quality)
private helperB = new BoundaryHelper(this.quality)
private checkSide(p: Vec3) {
return Vec3.dot(this.normal, Vec3.sub(this.tmpV, this.sphere.center, p)) > 0
}
includeStep(p: Vec3) {
if (this.checkSide(p)) {
this.helperA.includeStep(p)
} else {
this.helperB.includeStep(p)
}
}
finishedIncludeStep() {
this.helperA.finishedIncludeStep();
this.helperB.finishedIncludeStep();
}
radiusStep(p: Vec3) {
if (this.checkSide(p)) {
this.helperA.radiusStep(p)
} else {
this.helperB.radiusStep(p)
}
}
getSphere(): Sphere3D {
const sphereA = this.helperA.getSphere()
const sphereB = this.helperB.getSphere()
Sphere3D.expandBySphere(this.tmpS, this.sphere, sphereA)
Sphere3D.expandBySphere(this.tmpS, this.tmpS, sphereB)
// check if the split spheres actually result in a smaller radius
return this.tmpS.radius < this.sphere.radius ? {
center: Vec3.clone(this.sphere.center),
radius: this.sphere.radius,
hierarchy: [this.helperA.getSphere(), this.helperB.getSphere()]
} : Sphere3D.clone(this.sphere)
}
reset(sphere: Sphere3D, normal: Vec3) {
Sphere3D.copy(this.sphere, sphere)
Vec3.copy(this.normal, normal)
this.helperA.reset()
this.helperB.reset()
}
constructor(private quality: EposQuality) { }
}
type EposQuality = '6' | '14' | '26' | '98'
function getEposDir(quality: EposQuality) {

View File

@@ -0,0 +1,52 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { PositionData } from './common';
import { Vec3 } from '../linear-algebra';
import { OrderedSet } from '../../mol-data/int';
import { BoundaryHelper } from './boundary-helper';
import { Box3D, Sphere3D } from '../geometry';
const boundaryHelperCoarse = new BoundaryHelper('14');
const boundaryHelperFine = new BoundaryHelper('98');
function getBoundaryHelper(count: number) {
return count > 100_000 ? boundaryHelperCoarse : boundaryHelperFine
}
export type Boundary = { readonly box: Box3D, readonly sphere: Sphere3D }
export function getBoundary(data: PositionData): Boundary {
const { x, y, z, radius, indices } = data;
const p = Vec3();
const boundaryHelper = getBoundaryHelper(OrderedSet.size(indices));
boundaryHelper.reset();
for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
const i = OrderedSet.getAt(indices, t);
Vec3.set(p, x[i], y[i], z[i]);
boundaryHelper.includeSphereStep(p, (radius && radius[i]) || 0);
}
boundaryHelper.finishedIncludeStep();
for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
const i = OrderedSet.getAt(indices, t);
Vec3.set(p, x[i], y[i], z[i]);
boundaryHelper.radiusSphereStep(p, (radius && radius[i]) || 0);
}
const sphere = boundaryHelper.getSphere()
if (!radius && OrderedSet.size(indices) <= 98) {
const extrema: Vec3[] = []
for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
const i = OrderedSet.getAt(indices, t);
extrema.push(Vec3.create(x[i], y[i], z[i]));
}
Sphere3D.setExtrema(sphere, extrema)
}
return { box: boundaryHelper.getBox(), sphere };
}

View File

@@ -46,7 +46,7 @@ export const GaussianDensitySchema = {
}
export const GaussianDensityShaderCode = ShaderCode(
gaussian_density_vert, gaussian_density_frag,
'gaussian-density', gaussian_density_vert, gaussian_density_frag,
{ standardDerivatives: false, fragDepth: false }
)

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -11,14 +11,14 @@ import { Sphere3D } from '../primitives/sphere3d';
import { PositionData } from '../common';
import { Vec3 } from '../../linear-algebra';
import { OrderedSet } from '../../../mol-data/int';
import { BoundaryHelper } from '../boundary-helper';
import { Boundary } from '../../../mol-model/structure/structure/util/boundary';
interface GridLookup3D<T = number> extends Lookup3D<T> {
readonly buckets: { readonly offset: ArrayLike<number>, readonly count: ArrayLike<number>, readonly array: ArrayLike<number> }
}
function GridLookup3D<T extends number = number>(data: PositionData, cellSizeOrCount?: Vec3 | number): GridLookup3D<T> {
return new GridLookup3DImpl<T>(data, cellSizeOrCount);
function GridLookup3D<T extends number = number>(data: PositionData, boundary: Boundary, cellSizeOrCount?: Vec3 | number): GridLookup3D<T> {
return new GridLookup3DImpl<T>(data, boundary, cellSizeOrCount);
}
export { GridLookup3D }
@@ -48,8 +48,8 @@ class GridLookup3DImpl<T extends number = number> implements GridLookup3D<T> {
return query(this.ctx);
}
constructor(data: PositionData, cellSizeOrCount?: Vec3 | number) {
const structure = build(data, cellSizeOrCount);
constructor(data: PositionData, boundary: Boundary, cellSizeOrCount?: Vec3 | number) {
const structure = build(data, boundary, cellSizeOrCount);
this.ctx = createContext<T>(structure);
this.boundary = { box: structure.boundingBox, sphere: structure.boundingSphere };
this.buckets = { offset: structure.bucketOffset, count: structure.bucketCounts, array: structure.bucketArray };
@@ -166,36 +166,9 @@ function _build(state: BuildState): Grid3D {
}
}
const boundaryHelperCoarse = new BoundaryHelper('14');
const boundaryHelperFine = new BoundaryHelper('98');
function getBoundaryHelper(count: number) {
return count > 500_000 ? boundaryHelperCoarse : boundaryHelperFine
}
function getBoundary(data: PositionData) {
const { x, y, z, radius, indices } = data;
const p = Vec3();
const boundaryHelper = getBoundaryHelper(OrderedSet.size(indices));
boundaryHelper.reset();
for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
const i = OrderedSet.getAt(indices, t);
Vec3.set(p, x[i], y[i], z[i]);
boundaryHelper.includeSphereStep(p, (radius && radius[i]) || 0);
}
boundaryHelper.finishedIncludeStep();
for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
const i = OrderedSet.getAt(indices, t);
Vec3.set(p, x[i], y[i], z[i]);
boundaryHelper.radiusSphereStep(p, (radius && radius[i]) || 0);
}
return { boundingBox: boundaryHelper.getBox(), boundingSphere: boundaryHelper.getSphere() };
}
function build(data: PositionData, cellSizeOrCount?: Vec3 | number) {
const { boundingBox, boundingSphere } = getBoundary(data);
function build(data: PositionData, boundary: Boundary, cellSizeOrCount?: Vec3 | number) {
// need to expand the grid bounds to avoid rounding errors
const expandedBox = Box3D.expand(Box3D.empty(), boundingBox, Vec3.create(0.5, 0.5, 0.5));
const expandedBox = Box3D.expand(Box3D.empty(), boundary.box, Vec3.create(0.5, 0.5, 0.5));
const { indices } = data;
const S = Box3D.size(Vec3.zero(), expandedBox);
@@ -233,8 +206,8 @@ function build(data: PositionData, cellSizeOrCount?: Vec3 | number) {
size,
data: inputData,
expandedBox,
boundingBox,
boundingSphere,
boundingBox: boundary.box,
boundingSphere: boundary.sphere,
elementCount,
delta
}

View File

@@ -15,6 +15,7 @@ import { PositionData } from './common';
import { Mat4 } from '../../mol-math/linear-algebra/3d';
import { Box3D, GridLookup3D, fillGridDim } from '../../mol-math/geometry';
import { BaseGeometry } from '../../mol-geo/geometry/base';
import { Boundary } from '../../mol-model/structure/structure/util/boundary';
function normalToLine (out: Vec3, p: Vec3) {
out[0] = out[1] = out[2] = 1.0
@@ -54,7 +55,7 @@ export const DefaultMolecularSurfaceCalculationProps = PD.getDefaultValues(Molec
export type MolecularSurfaceCalculationProps = typeof DefaultMolecularSurfaceCalculationProps
export async function calcMolecularSurface(ctx: RuntimeContext, position: Required<PositionData>, maxRadius: number, box: Box3D | null, props: MolecularSurfaceCalculationProps) {
export async function calcMolecularSurface(ctx: RuntimeContext, position: Required<PositionData>, boundary: Boundary, maxRadius: number, box: Box3D | null, props: MolecularSurfaceCalculationProps) {
// Field generation method adapted from AstexViewer (Mike Hartshorn) by Fred Ludlow.
// Other parts based heavily on NGL (Alexander Rose) EDT Surface class
@@ -320,7 +321,7 @@ export async function calcMolecularSurface(ctx: RuntimeContext, position: Requir
const cellSize = Vec3.create(maxRadius, maxRadius, maxRadius)
Vec3.scale(cellSize, cellSize, 2)
const lookup3d = GridLookup3D(position, cellSize)
const lookup3d = GridLookup3D(position, boundary, cellSize)
const neighbours = lookup3d.result
if (box === null) box = lookup3d.boundary.box

View File

@@ -8,24 +8,23 @@
import { Vec3, Mat4, EPSILON } from '../../linear-algebra'
import { PositionData } from '../common'
import { OrderedSet } from '../../../mol-data/int';
import { NumberArray } from '../../../mol-util/type-helpers';
import { NumberArray, PickRequired } from '../../../mol-util/type-helpers';
import { Box3D } from './box3d';
import { Axes3D } from './axes3d';
type Sphere3D = Sphere3D.Data | Sphere3D.Hierarchy
interface Sphere3D {
center: Vec3,
radius: number,
extrema?: Vec3[]
}
function Sphere3D() {
return Sphere3D.zero();
}
namespace Sphere3D {
export interface Data { center: Vec3, radius: number }
export interface Hierarchy extends Data { hierarchy: Sphere3D[] }
export function isHierarchy(x: Sphere3D | Hierarchy): x is Hierarchy {
return 'hierarchy' in x
}
export function getList(sphere: Sphere3D) {
return Sphere3D.isHierarchy(sphere) ? sphere.hierarchy : [sphere]
export function hasExtrema(sphere: Sphere3D): sphere is PickRequired<Sphere3D, 'extrema'> {
return sphere.extrema !== undefined
}
export function create(center: Vec3, radius: number): Sphere3D { return { center, radius }; }
@@ -33,24 +32,27 @@ namespace Sphere3D {
export function clone(a: Sphere3D): Sphere3D {
const out = create(Vec3.clone(a.center), a.radius)
if (isHierarchy(a)) (out as Hierarchy).hierarchy = a.hierarchy
if (hasExtrema(a)) out.extrema = a.extrema
return out;
}
export function copy(out: Sphere3D, a: Sphere3D) {
Vec3.copy(out.center, a.center)
out.radius = a.radius
if (isHierarchy(a)) {
if (isHierarchy(out)) {
out.hierarchy.length = 0
out.hierarchy.push(...a.hierarchy)
} else {
(out as Hierarchy).hierarchy = a.hierarchy
}
}
if (hasExtrema(a)) setExtrema(out, a.extrema)
return out;
}
export function setExtrema(out: Sphere3D, extrema: Vec3[]): Sphere3D {
if (out.extrema !== undefined) {
out.extrema.length = 0
out.extrema.push(...extrema)
} else {
out.extrema = [...extrema]
}
return out
}
export function computeBounding(data: PositionData): Sphere3D {
const { x, y, z, indices } = data;
let cx = 0, cy = 0, cz = 0;
@@ -129,18 +131,18 @@ namespace Sphere3D {
return out
}
const tmpDir = Vec3()
/** Expand sphere radius by delta */
export function expand(out: Sphere3D, sphere: Sphere3D, delta: number): Sphere3D {
Vec3.copy(out.center, sphere.center)
out.radius = sphere.radius + delta
if (isHierarchy(sphere)) {
const hierarchy = sphere.hierarchy.map(s => expand(Sphere3D(), s, delta))
if (isHierarchy(out)) {
out.hierarchy.length = 0
out.hierarchy.push(...hierarchy)
} else {
(out as Hierarchy).hierarchy = hierarchy
}
if (hasExtrema(sphere)) {
setExtrema(out, sphere.extrema.map(e => {
Vec3.sub(tmpDir, e, sphere.center)
const dist = Vec3.distance(sphere.center, e)
Vec3.normalize(tmpDir, tmpDir)
return Vec3.scaleAndAdd(Vec3(), sphere.center, tmpDir, dist + delta)
}))
}
return out
}

View File

@@ -35,7 +35,8 @@ namespace SpacegroupCell {
export const Zero: SpacegroupCell = create('P 1', Vec3.create(1, 1, 1), Vec3.create(Math.PI / 2, Math.PI / 2, Math.PI / 2));
/** True if 'P 1' with cellsize [1, 1, 1] */
export function isZero(cell: SpacegroupCell) {
export function isZero(cell?: SpacegroupCell) {
if (!cell) return true;
return cell.index === 0 && cell.size[0] === 1 && cell.size[1] === 1 && cell.size[1] === 1;
}

View File

@@ -54,7 +54,7 @@ namespace CustomModelProperty {
attach: async (ctx: CustomProperty.Context, data: Model, props: Partial<PD.Values<Params>> = {}, addRef) => {
if (addRef) data.customProperties.reference(builder.descriptor, true);
const property = get(data)
const p = { ...property.props, ...props }
const p = PD.merge(builder.defaultParams, property.props, props)
if (property.data.value && PD.areEqual(builder.defaultParams, property.props, p)) return
const value = await builder.obtain(ctx, data, p)
data.customProperties.add(builder.descriptor);
@@ -64,7 +64,7 @@ namespace CustomModelProperty {
get: (data: Model) => get(data)?.data,
set: (data: Model, props: Partial<PD.Values<Params>> = {}) => {
const property = get(data)
const p = { ...property.props, ...props }
const p = PD.merge(builder.defaultParams, property.props, props)
if (!PD.areEqual(builder.defaultParams, property.props, p)) {
// this invalidates property.value
set(data, p, undefined)

View File

@@ -54,8 +54,9 @@ namespace CustomStructureProperty {
attach: async (ctx: CustomProperty.Context, data: Structure, props: Partial<PD.Values<Params>> = {}, addRef) => {
if (addRef) data.customPropertyDescriptors.reference(builder.descriptor, true);
if (builder.type === 'root') data = data.root
const rootProps = get(data.root).props
const property = get(data)
const p = { ...property.props, ...props }
const p = PD.merge(builder.defaultParams, rootProps, props)
if (property.data.value && PD.areEqual(builder.defaultParams, property.props, p)) return
const value = await builder.obtain(ctx, data, p)
data.customPropertyDescriptors.add(builder.descriptor);
@@ -66,7 +67,7 @@ namespace CustomStructureProperty {
set: (data: Structure, props: Partial<PD.Values<Params>> = {}, value?: Value) => {
if (builder.type === 'root') data = data.root
const property = get(data)
const p = { ...property.props, ...props }
const p = PD.merge(builder.defaultParams, property.props, props)
if (!PD.areEqual(builder.defaultParams, property.props, p)) {
// this invalidates property.value
set(data, p, value)

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -11,6 +11,7 @@ import { OrderedSet, SortedArray } from '../../../mol-data/int';
import { FeatureGroup, FeatureType } from './common';
import { ValenceModelProvider } from '../valence-model';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { getBoundary } from '../../../mol-math/geometry/boundary';
export { Features }
@@ -105,7 +106,11 @@ namespace Features {
return {
...data,
get lookup3d() {
return lookup3d || (lookup3d = GridLookup3D({ x: data.x, y: data.y, z: data.z, indices: OrderedSet.ofBounds(0 as FeatureIndex, data.count as FeatureIndex) }))
if (!lookup3d) {
const position = { x: data.x, y: data.y, z: data.z, indices: OrderedSet.ofBounds(0 as FeatureIndex, data.count as FeatureIndex) }
lookup3d = GridLookup3D(position, getBoundary(position))
}
return lookup3d
},
get elementsIndex() {
return elementsIndex || (elementsIndex = createElementsIndex(data, elementsCount))
@@ -128,7 +133,11 @@ namespace Features {
return {
indices,
get lookup3d() {
return lookup3d || (lookup3d = GridLookup3D({ x: data.x, y: data.y, z: data.z, indices }))
if (!lookup3d) {
const position = { x: data.x, y: data.y, z: data.z, indices }
lookup3d = GridLookup3D(position, getBoundary(position))
}
return lookup3d
}
}
}

View File

@@ -12,8 +12,8 @@ import { Unit } from '../../mol-model/structure/structure';
import { CustomStructureProperty } from '../common/custom-structure-property';
import { CustomProperty } from '../common/custom-property';
import { ModelSecondaryStructure } from '../../mol-model-formats/structure/property/secondary-structure';
import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
import { CustomPropertyDescriptor } from '../../mol-model/structure/common/custom-property';
import { Model } from '../../mol-model/structure/model';
function getSecondaryStructureParams(data?: Structure) {
let defaultType = 'model' as 'model' | 'dssp'
@@ -21,16 +21,11 @@ function getSecondaryStructureParams(data?: Structure) {
defaultType = 'dssp'
for (let i = 0, il = data.models.length; i < il; ++i) {
const m = data.models[i]
if (MmcifFormat.is(m.sourceData)) {
if (m.sourceData.data.db.struct_conf.id.isDefined ||
m.sourceData.data.db.struct_sheet_range.id.isDefined ||
m.sourceData.data.db.database_2.database_id.isDefined
) {
// if there is any secondary structure definition given or if there is
// an archival model, don't calculate dssp by default
defaultType = 'model'
break
}
if (Model.isFromPdbArchive(m) || Model.hasSecondaryStructure(m)) {
// if there is any secondary structure definition given or if there is
// an archival model, don't calculate dssp by default
defaultType = 'model'
break
}
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -8,6 +8,7 @@ import { GridLookup3D } from '../../../../mol-math/geometry';
import { SortedArray } from '../../../../mol-data/int';
import { Unit } from '../../../../mol-model/structure/structure';
import { ResidueIndex } from '../../../../mol-model/structure';
import { getBoundary } from '../../../../mol-math/geometry/boundary';
export function calcUnitProteinTraceLookup3D(unit: Unit.Atomic, unitProteinResidues: SortedArray<ResidueIndex>): GridLookup3D {
const { x, y, z } = unit.model.atomicConformation;
@@ -16,5 +17,6 @@ export function calcUnitProteinTraceLookup3D(unit: Unit.Atomic, unitProteinResid
for (let i = 0, il = unitProteinResidues.length; i < il; ++i) {
indices[i] = traceElementIndex[unitProteinResidues[i]]
}
return GridLookup3D({ x, y, z, indices: SortedArray.ofSortedArray(indices) });
const position = { x, y, z, indices: SortedArray.ofSortedArray(indices) }
return GridLookup3D(position, getBoundary(position));
}

View File

@@ -241,7 +241,7 @@ namespace Loci {
/**
* Converts structure related loci to StructureElement.Loci and applies
* granularity if given
*/
*/
export function normalize(loci: Loci, granularity?: Granularity) {
if (granularity !== 'element' && Bond.isLoci(loci)) {
// convert Bond.Loci to a StructureElement.Loci so granularity can be applied

View File

@@ -51,7 +51,7 @@ const struct_conf_fields: CifField[] = [
CifField.str<number, SSElement<SecondaryStructure.Helix>[]>('details', (i, data) => data[i].element.details || '', {
valueKind: (i, d) => !!d[i].element.details ? Column.ValueKind.Present : Column.ValueKind.Unknown
}),
CifField.int<number, SSElement<SecondaryStructure.Helix>[]>('pdbx_PDB_helix_class', (i, data) => data[i].length)
CifField.int<number, SSElement<SecondaryStructure.Helix>[]>('pdbx_PDB_helix_length', (i, data) => data[i].length)
];
const struct_sheet_range_fields: CifField[] = [

View File

@@ -21,6 +21,7 @@ import { Topology } from '../topology';
import { Task } from '../../../mol-task';
import { IndexPairBonds } from '../../../mol-model-formats/structure/property/bonds/index-pair';
import { createModels } from '../../../mol-model-formats/structure/basic/parser';
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
/**
* Interface to the "source data" of the molecule.
@@ -135,4 +136,82 @@ export namespace Model {
model._dynamicPropertyData[CenterProp] = center
return center
}
//
export function isFromPdbArchive(model: Model) {
if (!MmcifFormat.is(model.sourceData)) return false
const { db } = model.sourceData.data
return (
db.database_2.database_id.isDefined
)
}
export function hasSecondaryStructure(model: Model) {
if (!MmcifFormat.is(model.sourceData)) return false
const { db } = model.sourceData.data
return (
db.struct_conf.id.isDefined ||
db.struct_sheet_range.id.isDefined
)
}
export function hasCrystalSymmetry(model: Model) {
if (!MmcifFormat.is(model.sourceData)) return false
const { db } = model.sourceData.data
return (
db.symmetry._rowCount === 1 && db.cell._rowCount === 1 && !(
db.symmetry.Int_Tables_number.value(0) === 1 &&
db.cell.angle_alpha.value(0) === 90 &&
db.cell.angle_beta.value(0) === 90 &&
db.cell.angle_gamma.value(0) === 90 &&
db.cell.length_a.value(0) === 1 &&
db.cell.length_b.value(0) === 1 &&
db.cell.length_c.value(0) === 1
)
)
}
export function isFromXray(model: Model) {
if (!MmcifFormat.is(model.sourceData)) return false
const { db } = model.sourceData.data
for (let i = 0; i < db.exptl.method.rowCount; i++) {
const v = db.exptl.method.value(i).toUpperCase()
if (v.indexOf('DIFFRACTION') >= 0) return true
}
return false
}
export function isFromNmr(model: Model) {
if (!MmcifFormat.is(model.sourceData)) return false
const { db } = model.sourceData.data
for (let i = 0; i < db.exptl.method.rowCount; i++) {
const v = db.exptl.method.value(i).toUpperCase()
if (v.indexOf('NMR') >= 0) return true
}
return false
}
export function hasXrayMap(model: Model) {
if (!MmcifFormat.is(model.sourceData)) return false
const { db } = model.sourceData.data
return db.pdbx_database_status.status_code_sf.value(0) === 'REL'
}
export function hasEmMap(model: Model) {
if (!MmcifFormat.is(model.sourceData)) return false
const { db } = model.sourceData.data
let hasEmMap = false
for (let i = 0, il = db.pdbx_database_related._rowCount; i < il; ++i) {
if (db.pdbx_database_related.db_name.value(i).toUpperCase() === 'EMDB') {
hasEmMap = true
break
}
}
return hasEmMap
}
export function hasDensityMap(model: Model) {
return hasXrayMap(model) || hasEmMap(model)
}
}

View File

@@ -11,6 +11,7 @@ import { Model } from '../../model'
import { Spacegroup } from '../../../../mol-math/geometry';
import { Vec3 } from '../../../../mol-math/linear-algebra';
import { ModelSymmetry } from '../../../../mol-model-formats/structure/property/symmetry';
import { radToDeg } from '../../../../mol-math/misc';
/** Determine an atom set and a list of operators that should be applied to that set */
export interface OperatorGroup {
@@ -67,6 +68,25 @@ namespace Symmetry {
const symmetry = ModelSymmetry.Provider.get(model)
return symmetry ? arrayFind(symmetry.assemblies, a => a.id.toLowerCase() === _id) : undefined;
}
export function getUnitcellLabel(symmetry: Symmetry) {
const { cell, name, num } = symmetry.spacegroup
const { size, anglesInRadians } = cell
const a = size[0].toFixed(2)
const b = size[1].toFixed(2)
const c = size[2].toFixed(2)
const alpha = radToDeg(anglesInRadians[0]).toFixed(2)
const beta = radToDeg(anglesInRadians[1]).toFixed(2)
const gamma = radToDeg(anglesInRadians[2]).toFixed(2)
const label: string[] = []
// name
label.push(`Unitcell <b>${name}</b> #${num}`)
// sizes
label.push(`${a}\u00D7${b}\u00D7${c} \u212B`)
// angles
label.push(`\u03b1=${alpha}\u00B0 \u03b2=${beta}\u00B0 \u03b3=${gamma}\u00B0`)
return label.join(' | ')
}
}
export { Symmetry }

View File

@@ -28,6 +28,7 @@ import { UUID } from '../../../mol-util';
import { CustomProperties } from '../common/custom-property';
import { AtomicHierarchy } from '../model/properties/atomic';
import { StructureSelection } from '../query/selection';
import { getBoundary } from '../../../mol-math/geometry/boundary';
class Structure {
/** Maps unit.id to unit */
@@ -696,7 +697,8 @@ namespace Structure {
function partitionAtomicUnitByAtom(model: Model, indices: SortedArray, builder: StructureBuilder, multiChain: boolean) {
const { x, y, z } = model.atomicConformation;
const lookup = GridLookup3D({ x, y, z, indices }, 8192);
const position = { x, y, z, indices }
const lookup = GridLookup3D(position, getBoundary(position), 8192);
const { offset, count, array } = lookup.buckets;
const traits = (multiChain ? Unit.Trait.MultiChain : Unit.Trait.None) | (offset.length > 1 ? Unit.Trait.Partitioned : Unit.Trait.None);
@@ -731,7 +733,8 @@ namespace Structure {
const gridCellCount = 512 * firstResidueAtomCount
const { x, y, z } = model.atomicConformation;
const lookup = GridLookup3D({ x, y, z, indices: SortedArray.ofSortedArray(startIndices) }, gridCellCount);
const position = { x, y, z, indices: SortedArray.ofSortedArray(startIndices) }
const lookup = GridLookup3D(position, getBoundary(position), gridCellCount);
const { offset, count, array } = lookup.buckets;
const traits = (multiChain ? Unit.Trait.MultiChain : Unit.Trait.None) | (offset.length > 1 ? Unit.Trait.Partitioned : Unit.Trait.None);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -20,6 +20,7 @@ import { getAtomicPolymerElements, getCoarsePolymerElements, getAtomicGapElement
import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
import { PrincipalAxes } from '../../../mol-math/linear-algebra/matrix/principal-axes';
import { getPrincipalAxes } from './util/principal-axes';
import { Boundary, getBoundary } from '../../../mol-math/geometry/boundary';
/**
* A building block of a structure that corresponds to an atomic or
@@ -128,6 +129,7 @@ namespace Unit {
getChild(elements: StructureElement.Set): Unit,
applyOperator(id: number, operator: SymmetryOperator, dontCompose?: boolean /* = false */): Unit,
readonly boundary: Boundary
readonly lookup3d: Lookup3D<StructureElement.UnitIndex>
readonly polymerElements: SortedArray<ElementIndex>
readonly gapElements: SortedArray<ElementIndex>
@@ -138,6 +140,7 @@ namespace Unit {
}
interface BaseProperties {
boundary: ValueRef<Boundary | undefined>,
lookup3d: ValueRef<Lookup3D<StructureElement.UnitIndex> | undefined>,
principalAxes: ValueRef<PrincipalAxes | undefined>,
polymerElements: ValueRef<SortedArray<ElementIndex> | undefined>
@@ -147,6 +150,7 @@ namespace Unit {
function BaseProperties(): BaseProperties {
return {
boundary: ValueRef.create(void 0),
lookup3d: ValueRef.create(void 0),
principalAxes: ValueRef.create(void 0),
polymerElements: ValueRef.create(void 0),
@@ -203,10 +207,17 @@ namespace Unit {
return new Atomic(id, this.invariantId, this.chainGroupId, this.traits, this.model, this.elements, SymmetryOperator.createMapping(op, this.model.atomicConformation, this.conformation.r), this.props);
}
get boundary() {
if (this.props.boundary.ref) return this.props.boundary.ref;
const { x, y, z } = this.model.atomicConformation;
this.props.boundary.ref = getBoundary({ x, y, z, indices: this.elements });
return this.props.boundary.ref;
}
get lookup3d() {
if (this.props.lookup3d.ref) return this.props.lookup3d.ref;
const { x, y, z } = this.model.atomicConformation;
this.props.lookup3d.ref = GridLookup3D({ x, y, z, indices: this.elements });
this.props.lookup3d.ref = GridLookup3D({ x, y, z, indices: this.elements }, this.boundary);
return this.props.lookup3d.ref;
}
@@ -333,11 +344,19 @@ namespace Unit {
return ret;
}
get boundary() {
if (this.props.boundary.ref) return this.props.boundary.ref;
// TODO: support sphere radius?
const { x, y, z } = this.getCoarseConformation();
this.props.boundary.ref = getBoundary({ x, y, z, indices: this.elements });
return this.props.boundary.ref;
}
get lookup3d() {
if (this.props.lookup3d.ref) return this.props.lookup3d.ref;
// TODO: support sphere radius?
const { x, y, z } = this.getCoarseConformation();
this.props.lookup3d.ref = GridLookup3D({ x, y, z, indices: this.elements });
this.props.lookup3d.ref = GridLookup3D({ x, y, z, indices: this.elements }, this.boundary);
return this.props.lookup3d.ref;
}

View File

@@ -13,6 +13,7 @@ import { OrderedSet } from '../../../../mol-data/int';
import { StructureUniqueSubsetBuilder } from './unique-subset-builder';
import StructureElement from '../element';
import Unit from '../unit';
import { getBoundary } from '../../../../mol-math/geometry/boundary';
export interface StructureResult extends Result<StructureElement.UnitIndex> {
units: Unit[]
@@ -174,6 +175,7 @@ export class StructureLookup3D {
radius[i] = s.radius;
}
this.unitLookup = GridLookup3D({ x: xs, y: ys, z: zs, radius, indices: OrderedSet.ofBounds(0, unitCount) });
const position = { x: xs, y: ys, z: zs, radius, indices: OrderedSet.ofBounds(0, unitCount) }
this.unitLookup = GridLookup3D(position, getBoundary(position));
}
}

View File

@@ -18,6 +18,10 @@ import { StateTransforms } from '../transforms';
import { Download, ParsePsf } from '../transforms/data';
import { CoordinatesFromDcd, CustomModelProperties, CustomStructureProperties, TopologyFromPsf, TrajectoryFromModelAndCoordinates } from '../transforms/model';
import { DataFormatProvider, guessCifVariant } from './data-format';
import { applyTrajectoryHierarchyPreset } from '../builder/structure/hierarchy-preset';
import { PresetStructureReprentations } from '../builder/structure/representation-preset';
// TODO make unitcell creation part of preset
export const MmcifProvider: DataFormatProvider<PluginStateObject.Data.String | PluginStateObject.Data.Binary> = {
label: 'mmCIF',
@@ -31,11 +35,10 @@ export const MmcifProvider: DataFormatProvider<PluginStateObject.Data.String | P
return false
},
getDefaultBuilder: (ctx: PluginContext, data, options) => {
return Task.create('mmCIF default builder', async taskCtx => {
const { structure } = await ctx.builders.structure.parseStructure({ data, dataFormat: 'mmcif' });
if (options.visuals) {
await ctx.builders.structure.representation.applyPreset(structure, 'auto');
}
return Task.create('mmCIF default builder', async () => {
const trajectory = await ctx.builders.structure.parseTrajectory(data, 'mmcif');
const representationPreset = options.visuals ? 'auto' : 'empty';
await applyTrajectoryHierarchyPreset(ctx, trajectory, 'first-model', { showUnitcell: options.visuals, representationPreset });
})
}
}
@@ -50,10 +53,9 @@ export const PdbProvider: DataFormatProvider<any> = {
},
getDefaultBuilder: (ctx: PluginContext, data, options) => {
return Task.create('PDB default builder', async () => {
const { structure } = await ctx.builders.structure.parseStructure({ data, dataFormat: 'pdb' });
if (options.visuals) {
await ctx.builders.structure.representation.applyPreset(structure, 'auto');
}
const trajectory = await ctx.builders.structure.parseTrajectory(data, 'pdb');
const representationPreset = options.visuals ? 'auto' : 'empty';
await applyTrajectoryHierarchyPreset(ctx, trajectory, 'first-model', { showUnitcell: options.visuals, representationPreset });
})
}
}
@@ -68,10 +70,9 @@ export const GroProvider: DataFormatProvider<any> = {
},
getDefaultBuilder: (ctx: PluginContext, data, options) => {
return Task.create('GRO default builder', async () => {
const { structure } = await ctx.builders.structure.parseStructure({ data, dataFormat: 'gro' });
if (options.visuals) {
await ctx.builders.structure.representation.applyPreset(structure, 'auto');
}
const trajectory = await ctx.builders.structure.parseTrajectory(data, 'gro');
const representationPreset = options.visuals ? 'auto' : 'empty';
await applyTrajectoryHierarchyPreset(ctx, trajectory, 'first-model', { showUnitcell: options.visuals, representationPreset });
})
}
}
@@ -86,10 +87,9 @@ export const Provider3dg: DataFormatProvider<any> = {
},
getDefaultBuilder: (ctx: PluginContext, data, options) => {
return Task.create('3DG default builder', async () => {
const { structure } = await ctx.builders.structure.parseStructure({ data, dataFormat: '3dg' });
if (options.visuals) {
await ctx.builders.structure.representation.applyPreset(structure, 'auto');
}
const trajectory = await ctx.builders.structure.parseTrajectory(data, '3dg');
const representationPreset = options.visuals ? 'auto' : 'empty';
await applyTrajectoryHierarchyPreset(ctx, trajectory, 'first-model', { showUnitcell: options.visuals, representationPreset });
})
}
}
@@ -128,138 +128,131 @@ export const DcdProvider: DataFormatProvider<any> = {
//
const DownloadModelRepresentationOptions = PD.Group({
type: RootStructureDefinition.getParams(void 0, 'assembly').type,
noRepresentation: PD.Optional(PD.Boolean(false, { description: 'Omit creating default representation.' }))
}, { isExpanded: false });
const DownloadStructurePdbIdSourceOptions = PD.Group({
supportProps: PD.Optional(PD.Boolean(false)),
const DownloadModelRepresentationOptions = (plugin: PluginContext) => PD.Group({
type: RootStructureDefinition.getParams(void 0, 'auto').type,
representation: PD.Select(PresetStructureReprentations.auto.id,
plugin.builders.structure.representation.getPresets().map(p => [p.id, p.display.name] as any),
{ description: 'Which representation preset to use.' }),
asTrajectory: PD.Optional(PD.Boolean(false, { description: 'Load all entries into a single trajectory.' }))
});
}, { isExpanded: false });
export { DownloadStructure };
type DownloadStructure = typeof DownloadStructure
const DownloadStructure = StateAction.build({
from: PluginStateObject.Root,
display: { name: 'Download Structure', description: 'Load a structure from the provided source and create its representation.' },
params: {
source: PD.MappedStatic('bcif-static', {
'pdbe-updated': PD.Group({
id: PD.Text('1cbs', { label: 'PDB Id(s)', description: 'One or more comma separated PDB ids.' }),
structure: DownloadModelRepresentationOptions,
options: DownloadStructurePdbIdSourceOptions
}, { isFlat: true, label: 'PDBe Updated' }),
'rcsb': PD.Group({
id: PD.Text('1tqn', { label: 'PDB Id(s)', description: 'One or more comma separated PDB ids.' }),
structure: DownloadModelRepresentationOptions,
options: DownloadStructurePdbIdSourceOptions
}, { isFlat: true, label: 'RCSB' }),
'pdb-dev': PD.Group({
id: PD.Text('PDBDEV_00000001', { label: 'PDBDev Id(s)', description: 'One or more comma separated ids.' }),
structure: DownloadModelRepresentationOptions,
options: DownloadStructurePdbIdSourceOptions
}, { isFlat: true, label: 'PDBDEV' }),
'bcif-static': PD.Group({
id: PD.Text('1tqn', { label: 'PDB Id(s)', description: 'One or more comma separated PDB ids.' }),
structure: DownloadModelRepresentationOptions,
options: DownloadStructurePdbIdSourceOptions
}, { isFlat: true, label: 'BinaryCIF (static PDBe Updated)' }),
'swissmodel': PD.Group({
id: PD.Text('Q9Y2I8', { label: 'UniProtKB AC(s)', description: 'One or more comma separated ACs.' }),
structure: DownloadModelRepresentationOptions,
options: DownloadStructurePdbIdSourceOptions
}, { isFlat: true, label: 'SWISS-MODEL', description: 'Loads the best homology model or experimental structure' }),
'url': PD.Group({
url: PD.Text(''),
format: PD.Select('mmcif', [['mmcif', 'CIF'], ['pdb', 'PDB']] as ['mmcif' | 'pdb', string][]),
isBinary: PD.Boolean(false),
structure: DownloadModelRepresentationOptions,
options: PD.Group({
supportProps: PD.Optional(PD.Boolean(false)),
createRepresentation: PD.Optional(PD.Boolean(true))
})
}, { isFlat: true, label: 'URL' })
})
params: (_, plugin: PluginContext) => {
const options = DownloadModelRepresentationOptions(plugin);
return {
source: PD.MappedStatic('pdb', {
'pdb': PD.Group({
provider: PD.Group({
id: PD.Text('1tqn', { label: 'PDB Id(s)', description: 'One or more comma separated PDB ids.' }),
server: PD.MappedStatic('rcsb', {
'rcsb': PD.Group({
encoding: PD.Select('bcif', [['cif', 'cif'], ['bcif', 'bcif']] as ['cif' | 'bcif', string][]),
}, { label: 'RCSB PDB', isFlat: true }),
'pdbe': PD.Group({
variant: PD.Select('updated', [['updated', 'Updated'], ['archival', 'Archival']] as ['updated' | 'archival', string][]),
}, { label: 'PDBe', isFlat: true }),
}),
}, { pivot: 'id' }),
options
}, { isFlat: true, label: 'PDB' }),
'pdb-dev': PD.Group({
id: PD.Text('PDBDEV_00000001', { label: 'PDBDev Id(s)', description: 'One or more comma separated ids.' }),
options
}, { isFlat: true, label: 'PDBDEV' }),
'bcif-static': PD.Group({
id: PD.Text('1tqn', { label: 'PDB Id(s)', description: 'One or more comma separated PDB ids.' }),
options
}, { isFlat: true, label: 'BinaryCIF (static PDBe Updated)' }),
'swissmodel': PD.Group({
id: PD.Text('Q9Y2I8', { label: 'UniProtKB AC(s)', description: 'One or more comma separated ACs.' }),
options
}, { isFlat: true, label: 'SWISS-MODEL', description: 'Loads the best homology model or experimental structure' }),
'url': PD.Group({
url: PD.Text(''),
format: PD.Select('mmcif', [['mmcif', 'CIF'], ['pdb', 'PDB']] as ['mmcif' | 'pdb', string][]),
isBinary: PD.Boolean(false),
options
}, { isFlat: true, label: 'URL' })
})
}
}
})(({ params, state }, plugin: PluginContext) => Task.create('Download Structure', async ctx => {
plugin.behaviors.layout.leftPanelTabName.next('data');
const src = params.source;
let downloadParams: StateTransformer.Params<Download>[];
let supportProps = false, asTrajectory = false, format: BuiltInTrajectoryFormat = 'mmcif';
let asTrajectory = false, format: BuiltInTrajectoryFormat = 'mmcif';
switch (src.name) {
case 'url':
downloadParams = [{ url: src.params.url, isBinary: src.params.isBinary }];
supportProps = !!src.params.options.supportProps;
format = src.params.format
break;
case 'pdbe-updated':
downloadParams = getDownloadParams(src.params.id, id => `https://www.ebi.ac.uk/pdbe/static/entry/${id.toLowerCase()}_updated.cif`, id => `PDBe: ${id}`, false);
supportProps = !!src.params.options.supportProps;
asTrajectory = !!src.params.options.asTrajectory;
break;
case 'rcsb':
downloadParams = getDownloadParams(src.params.id, id => `https://files.rcsb.org/download/${id.toUpperCase()}.cif`, id => `RCSB: ${id}`, false);
supportProps = !!src.params.options.supportProps;
case 'pdb':
downloadParams = src.params.provider.server.name === 'pdbe'
? src.params.provider.server.params.variant === 'updated'
? getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/static/entry/${id.toLowerCase()}_updated.cif`, id => `PDBe: ${id} (updated cif)`, false)
: getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/static/entry/${id.toLowerCase()}.cif`, id => `PDBe: ${id} (cif)`, false)
: src.params.provider.server.params.encoding === 'cif'
? getDownloadParams(src.params.provider.id, id => `https://files.rcsb.org/download/${id.toUpperCase()}.cif`, id => `RCSB: ${id} (cif)`, false)
: getDownloadParams(src.params.provider.id, id => `https://models.rcsb.org/${id.toUpperCase()}.bcif`, id => `RCSB: ${id} (bcif)`, true);
asTrajectory = !!src.params.options.asTrajectory;
break;
case 'pdb-dev':
downloadParams = getDownloadParams(src.params.id,
id => {
const nId = id.toUpperCase().startsWith('PDBDEV_') ? id : `PDBDEV_${id.padStart(8, '0')}`
return `https://pdb-dev.wwpdb.org/static/cif/${nId.toUpperCase()}.cif`
return `https://pdb-dev.wwpdb.org/cif/${nId.toUpperCase()}.cif`
},
id => id.toUpperCase().startsWith('PDBDEV_') ? id : `PDBDEV_${id.padStart(8, '0')}`,
false
);
supportProps = !!src.params.options.supportProps;
asTrajectory = !!src.params.options.asTrajectory;
break;
case 'bcif-static':
downloadParams = getDownloadParams(src.params.id, id => `https://webchem.ncbr.muni.cz/ModelServer/static/bcif/${id.toLowerCase()}`, id => `BinaryCIF: ${id}`, true);
supportProps = !!src.params.options.supportProps;
asTrajectory = !!src.params.options.asTrajectory;
break;
case 'swissmodel':
downloadParams = getDownloadParams(src.params.id, id => `https://swissmodel.expasy.org/repository/uniprot/${id.toUpperCase()}.pdb`, id => `SWISS-MODEL: ${id}`, false);
supportProps = !!src.params.options.supportProps;
asTrajectory = !!src.params.options.asTrajectory;
format = 'pdb'
break;
default: throw new Error(`${(src as any).name} not supported.`);
}
const createRepr = !params.source.params.structure.noRepresentation;
const representationPreset: any = params.source.params.options.representation || PresetStructureReprentations.auto.id;
const showUnitcell = representationPreset !== PresetStructureReprentations.empty.id;
const structure = src.params.options.type.name === 'auto' ? void 0 : src.params.options.type;
await state.transaction(async () => {
if (downloadParams.length > 0 && asTrajectory) {
const blob = await plugin.builders.data.downloadBlob({
sources: downloadParams.map((src, i) => ({ id: '' + i, url: src.url, isBinary: src.isBinary })),
maxConcurrency: 6
}, { state: { isGhost: true } });
const { structure } = await plugin.builders.structure.parseStructure({
blob,
blobParams: { formats: downloadParams.map((_, i) => ({ id: '' + i, format: 'cif' as 'cif' })) },
modelProperties: supportProps,
structureProperties: supportProps
}, { state: { isGhost: true } });
const trajectory = await plugin.builders.structure.parseTrajectory(blob, { formats: downloadParams.map((_, i) => ({ id: '' + i, format: 'cif' as 'cif' })) });
await applyTrajectoryHierarchyPreset(plugin, trajectory, 'first-model', {
structure,
showUnitcell,
representationPreset
});
if (createRepr) {
await plugin.builders.structure.representation.applyPreset(structure, 'auto');
}
} else {
for (const download of downloadParams) {
const data = await plugin.builders.data.download(download, { state: { isGhost: true } });
const { structure } = await plugin.builders.structure.parseStructure({
data,
dataFormat: format,
modelProperties: supportProps,
structureProperties: supportProps
const trajectory = await plugin.builders.structure.parseTrajectory(data, format);
await applyTrajectoryHierarchyPreset(plugin, trajectory, 'first-model', {
structure,
showUnitcell,
representationPreset
});
if (createRepr) {
await plugin.builders.structure.representation.applyPreset(structure, 'auto');
}
}
}
}).runInContext(ctx);
@@ -300,10 +293,10 @@ export function createModelTree(b: StateBuilder.To<PluginStateObject.Data.Binary
export const Create3DRepresentationPreset = StateAction.build({
display: { name: '3D Representation Preset', description: 'Create one of preset 3D representations.' },
from: PluginStateObject.Molecule.Structure,
isApplicable(a, _, plugin: PluginContext) { return plugin.builders.structure.representation.hasPreset(a.data); },
isApplicable(a, _, plugin: PluginContext) { return plugin.builders.structure.representation.hasPreset(a); },
params(a, plugin: PluginContext) {
return {
type: plugin.builders.structure.representation.getPresetsWithOptions(a.data)
type: plugin.builders.structure.representation.getPresetsWithOptions(a)
};
}
})(({ ref, params }, plugin: PluginContext) => {

View File

@@ -110,23 +110,28 @@ const DownloadDensity = StateAction.build({
params: (a, ctx: PluginContext) => {
const { options } = ctx.dataFormat.registry
return {
source: PD.MappedStatic('rcsb', {
'pdbe': PD.Group({
id: PD.Text('1tqn', { label: 'Id' }),
source: PD.MappedStatic('pdb-xray', {
'pdb-xray': PD.Group({
provider: PD.Group({
id: PD.Text('1tqn', { label: 'Id' }),
server: PD.Select('rcsb', [['pdbe', 'PDBe'], ['rcsb', 'RCSB PDB']]),
}, { pivot: 'id' }),
type: PD.Select('2fofc', [['2fofc', '2Fo-Fc'], ['fofc', 'Fo-Fc']]),
}, { isFlat: true }),
'pdbe-emd-ds': PD.Group({
id: PD.Text('emd-8004', { label: 'Id' }),
'pdb-xray-ds': PD.Group({
provider: PD.Group({
id: PD.Text('1tqn', { label: 'Id' }),
server: PD.Select('pdbe', [['pdbe', 'PDBe'], ['rcsb', 'RCSB PDB']]),
}, { pivot: 'id' }),
detail: PD.Numeric(3, { min: 0, max: 10, step: 1 }, { label: 'Detail' }),
}, { isFlat: true }),
'pdbe-xray-ds': PD.Group({
id: PD.Text('1tqn', { label: 'Id' }),
'pdb-emd-ds': PD.Group({
provider: PD.Group({
id: PD.Text('emd-8004', { label: 'Id' }),
server: PD.Select('pdbe', [['pdbe', 'PDBe'], ['rcsb', 'RCSB PDB']]),
}, { pivot: 'id' }),
detail: PD.Numeric(3, { min: 0, max: 10, step: 1 }, { label: 'Detail' }),
}, { isFlat: true }),
'rcsb': PD.Group({
id: PD.Text('1tqn', { label: 'Id' }),
type: PD.Select('2fofc', [['2fofc', '2Fo-Fc'], ['fofc', 'Fo-Fc']]),
}, { isFlat: true }),
'url': PD.Group({
url: PD.Text(''),
isBinary: PD.Boolean(false),
@@ -134,10 +139,9 @@ const DownloadDensity = StateAction.build({
}, { isFlat: true })
}, {
options: [
['pdbe', 'PDBe X-ray maps'],
['pdbe-emd-ds', 'PDBe EMD Density Server'],
['pdbe-xray-ds', 'PDBe X-ray Density Server'],
['rcsb', 'RCSB X-ray maps'],
['pdb-xray', 'PDB X-ray maps'],
['pdb-emd-ds', 'PDB EMD Density Server'],
['pdb-xray-ds', 'PDB X-ray Density Server'],
['url', 'URL']
]
})
@@ -152,59 +156,62 @@ const DownloadDensity = StateAction.build({
case 'url':
downloadParams = src.params;
break;
case 'pdbe':
downloadParams = {
case 'pdb-xray':
downloadParams = src.params.provider.server === 'pdbe' ? {
url: src.params.type === '2fofc'
? `http://www.ebi.ac.uk/pdbe/coordinates/files/${src.params.id.toLowerCase()}.ccp4`
: `http://www.ebi.ac.uk/pdbe/coordinates/files/${src.params.id.toLowerCase()}_diff.ccp4`,
? `http://www.ebi.ac.uk/pdbe/coordinates/files/${src.params.provider.id.toLowerCase()}.ccp4`
: `http://www.ebi.ac.uk/pdbe/coordinates/files/${src.params.provider.id.toLowerCase()}_diff.ccp4`,
isBinary: true,
label: `PDBe X-ray map: ${src.params.id}`
};
break;
case 'pdbe-emd-ds':
downloadParams = {
url: `https://www.ebi.ac.uk/pdbe/densities/emd/${src.params.id.toLowerCase()}/cell?detail=${src.params.detail}`,
isBinary: true,
label: `PDBe EMD Density Server: ${src.params.id}`
};
break;
case 'pdbe-xray-ds':
downloadParams = {
url: `https://www.ebi.ac.uk/pdbe/densities/x-ray/${src.params.id.toLowerCase()}/cell?detail=${src.params.detail}`,
isBinary: true,
label: `PDBe X-ray Density Server: ${src.params.id}`
};
break;
case 'rcsb':
downloadParams = {
label: `PDBe X-ray map: ${src.params.provider.id}`
} : {
url: src.params.type === '2fofc'
? `https://edmaps.rcsb.org/maps/${src.params.id.toLowerCase()}_2fofc.dsn6`
: `https://edmaps.rcsb.org/maps/${src.params.id.toLowerCase()}_fofc.dsn6`,
? `https://edmaps.rcsb.org/maps/${src.params.provider.id.toLowerCase()}_2fofc.dsn6`
: `https://edmaps.rcsb.org/maps/${src.params.provider.id.toLowerCase()}_fofc.dsn6`,
isBinary: true,
label: `RCSB X-ray map: ${src.params.id}`
label: `RCSB X-ray map: ${src.params.provider.id}`
};
break;
case 'pdb-emd-ds':
downloadParams = src.params.provider.server === 'pdbe' ? {
url: `https://www.ebi.ac.uk/pdbe/densities/emd/${src.params.provider.id.toLowerCase()}/cell?detail=${src.params.detail}`,
isBinary: true,
label: `PDBe EMD Density Server: ${src.params.provider.id}`
} : {
url: `https://maps.rcsb.org/em/${src.params.provider.id.toLowerCase()}/cell?detail=${src.params.detail}`,
isBinary: true,
label: `RCSB PDB EMD Density Server: ${src.params.provider.id}`
};
break;
case 'pdb-xray-ds':
downloadParams = src.params.provider.server === 'pdbe' ? {
url: `https://www.ebi.ac.uk/pdbe/densities/x-ray/${src.params.provider.id.toLowerCase()}/cell?detail=${src.params.detail}`,
isBinary: true,
label: `PDBe X-ray Density Server: ${src.params.provider.id}`
} : {
url: `https://maps.rcsb.org/x-ray/${src.params.provider.id.toLowerCase()}/cell?detail=${src.params.detail}`,
isBinary: true,
label: `RCSB PDB X-ray Density Server: ${src.params.provider.id}`
};
break;
default: throw new Error(`${(src as any).name} not supported.`);
}
const data = await ctx.builders.data.download(downloadParams);
//const dataStateObject = await state.updateTree(data).runInContext(taskCtx);
switch (src.name) {
case 'url':
downloadParams = src.params;
provider = src.params.format === 'auto' ? ctx.dataFormat.registry.auto(getFileInfo(downloadParams.url), data.cell?.obj!) : ctx.dataFormat.registry.get(src.params.format)
break;
case 'pdbe':
provider = ctx.dataFormat.registry.get('ccp4')
case 'pdb-xray':
provider = src.params.provider.server === 'pdbe'
? ctx.dataFormat.registry.get('ccp4')
: ctx.dataFormat.registry.get('dsn6')
break;
case 'pdbe-emd-ds':
case 'pdbe-xray-ds':
case 'pdb-emd-ds':
case 'pdb-xray-ds':
provider = ctx.dataFormat.registry.get('dscif')
break;
case 'rcsb':
provider = ctx.dataFormat.registry.get('dsn6')
break;
default: throw new Error(`${(src as any).name} not supported.`);
}

View File

@@ -26,7 +26,7 @@ export class DataBuilder {
return data.selector;
}
async downloadBlob(params: StateTransformer.Params<DownloadBlob>, options?: Partial<StateTransform.Options>) {
async downloadBlob(params: StateTransformer.Params<DownloadBlob>, options?: Partial<StateTransform.Options>) {
const data = this.dataState.build().toRoot().apply(DownloadBlob, params, options);
await this.plugin.updateDataState(data, { revertOnError: true });
return data.selector;

View File

@@ -0,0 +1,17 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { StateObject, StateObjectRef } from '../../mol-state';
import { PluginContext } from '../../mol-plugin/context';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
export interface PresetProvider<O extends StateObject = StateObject, P = any, S = {}> {
id: string,
display: { name: string, group: string, description?: string },
isApplicable?(a: O, plugin: PluginContext): boolean,
params?(a: O | undefined, plugin: PluginContext): PD.For<P>,
apply(a: StateObjectRef<O>, params: P, plugin: PluginContext): Promise<S> | S
}

View File

@@ -15,13 +15,18 @@ import { StructureRepresentationBuilder } from './structure/representation';
import { StructureSelectionQuery } from '../helpers/structure-selection-query';
import { Task } from '../../mol-task';
import { StructureElement } from '../../mol-model/structure';
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
import { SpacegroupCell } from '../../mol-math/geometry';
import Expression from '../../mol-script/language/expression';
export type TrajectoryFormat = 'pdb' | 'cif' | 'gro' | '3dg'
export enum StructureBuilderTags {
Trajectory = 'trajectory',
Model = 'model',
ModelUnitcell = 'model-unitcell',
ModelProperties = 'model-properties',
ModelGenericRepresentation = 'model-generic-representation',
Structure = 'structure',
StructureProperties = 'structure-properties',
Component = 'structure-component'
@@ -43,45 +48,45 @@ export class StructureBuilder {
const state = this.dataState;
const trajectory = state.build().to(data)
.apply(StateTransforms.Data.ParseBlob, params, { state: { isGhost: true } })
.apply(StateTransforms.Model.TrajectoryFromBlob, void 0, { tags: StructureBuilderTags.Trajectory });
.apply(StateTransforms.Model.TrajectoryFromBlob, void 0, { tags: StructureBuilderTags.Trajectory });
await this.plugin.updateDataState(trajectory, { revertOnError: true });
return trajectory.selector;
}
readonly representation = new StructureRepresentationBuilder(this.plugin);
async parseStructure(params: {
data?: StateObjectRef<SO.Data.Binary | SO.Data.String>,
dataFormat?: BuiltInTrajectoryFormat | TrajectoryFormatProvider,
blob?: StateObjectRef<SO.Data.Blob>
blobParams?: StateTransformer.Params<StateTransforms['Data']['ParseBlob']>,
model?: StateTransformer.Params<StateTransforms['Model']['ModelFromTrajectory']>,
modelProperties?: boolean | StateTransformer.Params<StateTransforms['Model']['CustomModelProperties']>,
structure?: RootStructureDefinition.Params,
structureProperties?: boolean | StateTransformer.Params<StateTransforms['Model']['CustomStructureProperties']>
}) {
const trajectory = params.data
? await this.parseTrajectory(params.data, params.dataFormat! || 'cif')
: await this.parseTrajectoryBlob(params.blob!, params.blobParams!);
const model = await this.createModel(trajectory, params.model);
const modelProperties = !!params.modelProperties
? await this.insertModelProperties(model, typeof params?.modelProperties !== 'boolean' ? params?.modelProperties : void 0) : void 0;
const structure = await this.createStructure(modelProperties || model, params.structure);
const structureProperties = !!params.structureProperties
? await this.insertStructureProperties(structure, typeof params?.structureProperties !== 'boolean' ? params?.structureProperties : void 0) : void 0;
return {
trajectory,
model: modelProperties || model,
modelRoot: model,
modelProperties,
structure: structureProperties || structure,
structureRoot: structure,
structureProperties
};
}
// async parseStructure(params: {
// data?: StateObjectRef<SO.Data.Binary | SO.Data.String>,
// dataFormat?: BuiltInTrajectoryFormat | TrajectoryFormatProvider,
// blob?: StateObjectRef<SO.Data.Blob>
// blobParams?: StateTransformer.Params<StateTransforms['Data']['ParseBlob']>,
// model?: StateTransformer.Params<StateTransforms['Model']['ModelFromTrajectory']>,
// modelProperties?: boolean | StateTransformer.Params<StateTransforms['Model']['CustomModelProperties']>,
// structure?: RootStructureDefinition.Params,
// structureProperties?: boolean | StateTransformer.Params<StateTransforms['Model']['CustomStructureProperties']>
// }) {
// const trajectory = params.data
// ? await this.parseTrajectory(params.data, params.dataFormat! || 'cif')
// : await this.parseTrajectoryBlob(params.blob!, params.blobParams!);
// const model = await this.createModel(trajectory, params.model);
// const modelProperties = !!params.modelProperties
// ? await this.insertModelProperties(model, typeof params?.modelProperties !== 'boolean' ? params?.modelProperties : void 0) : void 0;
// const structure = await this.createStructure(modelProperties || model, params.structure);
// const structureProperties = !!params.structureProperties
// ? await this.insertStructureProperties(structure, typeof params?.structureProperties !== 'boolean' ? params?.structureProperties : void 0) : void 0;
// return {
// trajectory,
// model: modelProperties || model,
// modelRoot: model,
// modelProperties,
// structure: structureProperties || structure,
// structureRoot: structure,
// structureProperties
// };
// }
async parseTrajectory(data: StateObjectRef<SO.Data.Binary | SO.Data.String>, format: BuiltInTrajectoryFormat | TrajectoryFormatProvider): Promise<StateObjectSelector<SO.Molecule.Trajectory>>
async parseTrajectory(blob: StateObjectRef<SO.Data.Blob>, params: StateTransformer.Params<StateTransforms['Data']['ParseBlob']>): Promise<StateObjectSelector<SO.Molecule.Trajectory>>
@@ -113,10 +118,32 @@ export class StructureBuilder {
return props.selector;
}
async createStructure(model: StateObjectRef<SO.Molecule.Model>, params?: RootStructureDefinition.Params, initialState?: Partial<StateTransform.State>) {
async tryCreateUnitcell(model: StateObjectRef<SO.Molecule.Model>, params?: StateTransformer.Params<StateTransforms['Representation']['ModelUnitcell3D']>, initialState?: Partial<StateTransform.State>) {
const state = this.dataState;
const structure = state.build().to(model)
.apply(StateTransforms.Model.StructureFromModel, { type: params || { name: 'assembly', params: { } } }, { tags: StructureBuilderTags.Structure, state: initialState });
const m = StateObjectRef.resolveAndCheck(state, model)?.obj?.data;
if (!m) return;
const cell = ModelSymmetry.Provider.get(m)?.spacegroup.cell;
if (SpacegroupCell.isZero(cell)) return;
const unitcell = state.build().to(model)
.apply(StateTransforms.Representation.ModelUnitcell3D, params, { tags: StructureBuilderTags.ModelUnitcell, state: initialState });
await this.plugin.updateDataState(unitcell, { revertOnError: true });
return unitcell.selector;
}
async createStructure(modelRef: StateObjectRef<SO.Molecule.Model>, params?: RootStructureDefinition.Params, initialState?: Partial<StateTransform.State>) {
const state = this.dataState;
if (!params) {
const model = StateObjectRef.resolveAndCheck(state, modelRef);
if (model) {
const symm = ModelSymmetry.Provider.get(model.obj?.data!);
if (!symm || symm?.assemblies.length === 0) params = { name: 'deposited', params: { } };
}
}
const structure = state.build().to(modelRef)
.apply(StateTransforms.Model.StructureFromModel, { type: params || { name: 'assembly', params: { } } }, { tags: StructureBuilderTags.Structure, state: initialState });
await this.plugin.updateDataState(structure, { revertOnError: true });
return structure.selector;
@@ -141,7 +168,7 @@ export class StructureBuilder {
const root = state.build().to(structure);
const keyTag = `structure-component-${key}`;
const component = root.applyOrUpdateTagged(keyTag, StateTransforms.Model.StructureComponent, params, {
const component = root.applyOrUpdateTagged(keyTag, StateTransforms.Model.StructureComponent, params, {
tags: tags ? [...tags, StructureBuilderTags.Component, keyTag] : [StructureBuilderTags.Component, keyTag]
});
@@ -158,58 +185,49 @@ export class StructureBuilder {
return selector;
}
tryCreateStaticComponent(params: { structure: StateObjectRef<SO.Molecule.Structure>, type: StaticStructureComponentType, key: string, label?: string, tags?: string[] }) {
return this.tryCreateComponent(params.structure, {
type: { name: 'static', params: params.type },
tryCreateComponentFromExpression(structure: StateObjectRef<SO.Molecule.Structure>, expression: Expression, key: string, params?: { label?: string, tags?: string[] }) {
return this.tryCreateComponent(structure, {
type: { name: 'expression', params: expression },
nullIfEmpty: true,
label: ''
}, params.key, params.tags);
label: (params?.label || '').trim()
}, key, params?.tags);
}
tryCreateQueryComponent(params: { structure: StateObjectRef<SO.Molecule.Structure>, query: StructureSelectionQuery, key: string, label?: string, tags?: string[] }): Promise<StateObjectRef<SO.Molecule.Structure> | undefined> {
tryCreateComponentStatic(structure: StateObjectRef<SO.Molecule.Structure>, type: StaticStructureComponentType, key: string, params?: { label?: string, tags?: string[] }) {
return this.tryCreateComponent(structure, {
type: { name: 'static', params: type },
nullIfEmpty: true,
label: (params?.label || '').trim()
}, key, params?.tags);
}
tryCreateComponentFromSelection(structure: StateObjectRef<SO.Molecule.Structure>, selection: StructureSelectionQuery, key: string, params?: { label?: string, tags?: string[] }): Promise<StateObjectRef<SO.Molecule.Structure> | undefined> {
return this.plugin.runTask(Task.create('Query Component', async taskCtx => {
let { structure, query, key, label, tags } = params;
let { label, tags } = params || { };
label = (label || '').trim();
const structureData = StateObjectRef.resolveAndCheck(this.dataState, structure)?.obj?.data;
if (!structureData) return;
const transformParams: StructureComponentParams = query.referencesCurrent
const transformParams: StructureComponentParams = selection.referencesCurrent
? {
type: {
name: 'bundle',
params: StructureElement.Bundle.fromSelection(await query.getSelection(this.plugin, taskCtx, structureData)) },
params: StructureElement.Bundle.fromSelection(await selection.getSelection(this.plugin, taskCtx, structureData)) },
nullIfEmpty: true,
label: label || query.label
label: label || selection.label
} : {
type: { name: 'expression', params: query.expression },
type: { name: 'expression', params: selection.expression },
nullIfEmpty: true,
label: label || query.label
label: label || selection.label
};
if (query.ensureCustomProperties) {
await query.ensureCustomProperties({ fetch: this.plugin.fetch, runtime: taskCtx }, structureData);
if (selection.ensureCustomProperties) {
await selection.ensureCustomProperties({ fetch: this.plugin.fetch, runtime: taskCtx }, structureData);
}
const state = this.dataState;
const root = state.build().to(structure);
const keyTag = `structure-component-${key}`;
const component = root.applyOrUpdateTagged(keyTag, StateTransforms.Model.StructureComponent, transformParams, {
tags: tags ? [...tags, StructureBuilderTags.Component, keyTag] : [StructureBuilderTags.Component, keyTag]
});
await this.dataState.updateTree(component).runInContext(taskCtx);
const selector = component.selector;
if (!selector.isOk || selector.cell?.obj?.data.elementCount === 0) {
const del = state.build().delete(selector.ref);
await this.plugin.updateDataState(del);
return;
}
return selector;
return this.tryCreateComponent(structure, transformParams, key, tags);
}))
}

View File

@@ -0,0 +1,117 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { PresetProvider } from '../preset-provider';
import { PluginStateObject } from '../../objects';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { StateObjectRef, StateTransformer } from '../../../mol-state';
import { StateTransforms } from '../../transforms';
import { RootStructureDefinition } from '../../helpers/root-structure';
import { PresetStructureReprentations } from './representation-preset';
import { PluginContext } from '../../../mol-plugin/context';
import { isProductionMode } from '../../../mol-util/debug';
import { Task } from '../../../mol-task';
export interface TrajectoryHierarchyPresetProvider<P = any, S = {}> extends PresetProvider<PluginStateObject.Molecule.Trajectory, P, S> { }
export namespace TrajectoryHierarchyPresetProvider {
export type Params<P extends TrajectoryHierarchyPresetProvider> = P extends TrajectoryHierarchyPresetProvider<infer T> ? T : never;
export type State<P extends TrajectoryHierarchyPresetProvider> = P extends TrajectoryHierarchyPresetProvider<infer _, infer S> ? S : never;
}
export function TrajectoryHierarchyPresetProvider<P, S>(preset: TrajectoryHierarchyPresetProvider<P, S>) { return preset; }
const CommonParams = (a: PluginStateObject.Molecule.Trajectory | undefined, plugin: PluginContext) => ({
modelProperties: PD.Optional(PD.Group(StateTransformer.getParamDefinition(StateTransforms.Model.CustomModelProperties, void 0, plugin))),
structureProperties: PD.Optional(PD.Group(StateTransformer.getParamDefinition(StateTransforms.Model.CustomStructureProperties, void 0, plugin))),
representationPreset: PD.Optional(PD.Text<keyof PresetStructureReprentations>('auto' as const)),
})
const FirstModelParams = (a: PluginStateObject.Molecule.Trajectory | undefined, plugin: PluginContext) => ({
model: PD.Optional(PD.Group(StateTransformer.getParamDefinition(StateTransforms.Model.ModelFromTrajectory, a, plugin))),
showUnitcell: PD.Optional(PD.Boolean(true)),
structure: PD.Optional(RootStructureDefinition.getParams(void 0, 'assembly').type),
...CommonParams(a, plugin)
});
const firstModel = TrajectoryHierarchyPresetProvider({
id: 'preset-trajectory-first-model',
display: { name: 'First Model', group: 'Preset' },
params: FirstModelParams,
async apply(trajectory, params, plugin) {
const builder = plugin.builders.structure;
const model = await builder.createModel(trajectory, params.model);
const modelProperties = await builder.insertModelProperties(model, params.modelProperties);
const structure = await builder.createStructure(modelProperties || model, params.structure);
const structureProperties = await builder.insertStructureProperties(structure, params.structureProperties);
const unitcell = params.showUnitcell === void 0 || !!params.showUnitcell ? await builder.tryCreateUnitcell(modelProperties, undefined, { isHidden: true }) : void 0;
const representation = await plugin.builders.structure.representation.applyPreset(structureProperties, params.representationPreset || 'auto');
return {
model: modelProperties,
modelRoot: model,
modelProperties,
unitcell,
structure: structureProperties,
structureRoot: structure,
structureProperties,
representation
};
}
});
const allModels = TrajectoryHierarchyPresetProvider({
id: 'preset-trajectory-all-models',
display: { name: 'All Models', group: 'Preset' },
params: CommonParams,
async apply(trajectory, params, plugin) {
const tr = StateObjectRef.resolveAndCheck(plugin.state.data, trajectory)?.obj?.data;
if (!tr) return { };
const builder = plugin.builders.structure;
const models = [], structures = [];
for (let i = 0; i < tr.length; i++) {
const model = await builder.createModel(trajectory, { modelIndex: i }, { isCollapsed: true });
const modelProperties = await builder.insertModelProperties(model, params.modelProperties);
const structure = await builder.createStructure(modelProperties || model, { name: 'deposited', params: {} });
const structureProperties = await builder.insertStructureProperties(structure, params.structureProperties);
models.push(model);
structures.push(structure);
await builder.representation.applyPreset(structureProperties, params.representationPreset || 'auto', { globalThemeName: 'model-index' });
}
return { models, structures };
}
});
export const PresetStructureTrajectoryHierarchy = {
'first-model': firstModel,
'all-models': allModels
};
export type PresetStructureTrajectoryHierarchy = typeof PresetStructureTrajectoryHierarchy;
// TODO: should there be a registry like for representations?
export function applyTrajectoryHierarchyPreset<K extends keyof PresetStructureTrajectoryHierarchy>(plugin: PluginContext, parent: StateObjectRef<PluginStateObject.Molecule.Trajectory>, preset: K, params?: Partial<TrajectoryHierarchyPresetProvider.Params<PresetStructureTrajectoryHierarchy[K]>>): Promise<TrajectoryHierarchyPresetProvider.State<PresetStructureTrajectoryHierarchy[K]>> | undefined
export function applyTrajectoryHierarchyPreset<P = any, S = {}>(plugin: PluginContext, parent: StateObjectRef<PluginStateObject.Molecule.Trajectory>, provider: TrajectoryHierarchyPresetProvider<P, S>, params?: P): Promise<S> | undefined
export function applyTrajectoryHierarchyPreset(plugin: PluginContext, parent: StateObjectRef, providerRef: string | TrajectoryHierarchyPresetProvider, params?: any): Promise<any> | undefined {
const provider = typeof providerRef === 'string' ? (PresetStructureTrajectoryHierarchy as any)[providerRef] : providerRef;
if (!provider) return;
const state = plugin.state.data;
const cell = StateObjectRef.resolveAndCheck(state, parent);
if (!cell) {
if (!isProductionMode) console.warn(`Applying hierarchy preset provider to bad cell.`);
return;
}
const prms = { ...PD.getDefaultValues(provider.params(cell.obj!, plugin) as PD.Params), ...params };
const task = Task.create(`${provider.display.name}`, () => provider.apply(cell, prms, plugin) as Promise<any>);
return plugin.runTask(task);
}

View File

@@ -1,32 +0,0 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { PluginContext } from '../../../mol-plugin/context';
import { State, StateObjectCell } from '../../../mol-state';
import { RuntimeContext } from '../../../mol-task';
import { Structure } from '../../../mol-model/structure';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { PluginStateObject } from '../../objects';
export interface StructureRepresentationProvider<P = any, S = {}> {
id: string,
display: { name: string, group: string, description?: string },
isApplicable?(structure: Structure, plugin: PluginContext): boolean,
params?(structure: Structure | undefined, plugin: PluginContext): PD.For<P>,
apply(ctx: RuntimeContext, state: State, structure: StateObjectCell<PluginStateObject.Molecule.Structure>, params: P, plugin: PluginContext): Promise<S> | S
}
export namespace StructureRepresentationProvider {
export type Params<P extends StructureRepresentationProvider> = P extends StructureRepresentationProvider<infer T> ? T : never;
export type State<P extends StructureRepresentationProvider> = P extends StructureRepresentationProvider<infer _, infer S> ? S : never;
}
export const enum RepresentationProviderTags {
Representation = 'structure-representation',
Component = 'preset-structure-component'
}
export function StructureRepresentationProvider<P, S>(repr: StructureRepresentationProvider<P, S>) { return repr; }

View File

@@ -5,42 +5,50 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { PresetProvider } from '../preset-provider';
import { PluginStateObject } from '../../objects';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { VisualQuality, VisualQualityOptions } from '../../../mol-geo/geometry/base';
import { ColorTheme } from '../../../mol-theme/color';
import { Structure } from '../../../mol-model/structure';
import { PluginContext } from '../../../mol-plugin/context';
import { StateObjectRef } from '../../../mol-state';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { StaticStructureComponentType } from '../../helpers/structure-component';
import { StructureSelectionQueries as Q } from '../../helpers/structure-selection-query';
import { PluginStateObject } from '../../objects';
import { RepresentationProviderTags, StructureRepresentationProvider } from './provider';
import { ColorTheme } from '../../../mol-theme/color';
export const CommonStructureRepresentationParams = {
export interface StructureRepresentationPresetProvider<P = any, S = {}> extends PresetProvider<PluginStateObject.Molecule.Structure, P, S> { }
export namespace StructureRepresentationPresetProvider {
export type Params<P extends StructureRepresentationPresetProvider> = P extends StructureRepresentationPresetProvider<infer T> ? T : never;
export type State<P extends StructureRepresentationPresetProvider> = P extends StructureRepresentationPresetProvider<infer _, infer S> ? S : never;
}
export function StructureRepresentationPresetProvider<P, S>(repr: StructureRepresentationPresetProvider<P, S>) { return repr; }
export const CommonStructureRepresentationPresetParams = {
ignoreHydrogens: PD.Optional(PD.Boolean(false)),
quality: PD.Optional(PD.Select<VisualQuality>('auto', VisualQualityOptions)),
globalThemeName: PD.Optional(PD.Text<ColorTheme.BuiltIn>(''))
}
export type CommonStructureRepresentationParams = PD.ValuesFor<typeof CommonStructureRepresentationParams>
export type CommonStructureRepresentationParams = PD.ValuesFor<typeof CommonStructureRepresentationPresetParams>
const auto = StructureRepresentationProvider({
const auto = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-auto',
display: { name: 'Automatic', group: 'Preset' },
params: () => CommonStructureRepresentationParams,
apply(ctx, state, structureCell, params, plugin) {
const structure = structureCell.obj!.data;
params: () => CommonStructureRepresentationPresetParams,
apply(ref, params, plugin) {
const structure = StateObjectRef.resolveAndCheck(plugin.state.data, ref)?.obj?.data;
if (!structure) return { };
const size = Structure.getSize(structure)
switch (size) {
case Structure.Size.Gigantic:
case Structure.Size.Huge:
return coarseSurface.apply(ctx, state, structureCell, params, plugin);
return coarseSurface.apply(ref, params, plugin);
case Structure.Size.Large:
return polymerCartoon.apply(ctx, state, structureCell, params, plugin);
return polymerCartoon.apply(ref, params, plugin);
case Structure.Size.Medium:
return polymerAndLigand.apply(ctx, state, structureCell, params, plugin);
return polymerAndLigand.apply(ref, params, plugin);
case Structure.Size.Small:
return atomicDetail.apply(ctx, state, structureCell, params, plugin);
return atomicDetail.apply(ref, params, plugin);
}
}
});
@@ -59,11 +67,22 @@ function reprBuilder(plugin: PluginContext, params: CommonStructureRepresentatio
return { update, builder, color, typeParams };
}
const polymerAndLigand = StructureRepresentationProvider({
const empty = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-empty',
display: { name: 'Empty', group: 'Preset' },
async apply(ref, params, plugin) {
return { };
}
});
const polymerAndLigand = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-polymer-and-ligand',
display: { name: 'Polymer & Ligand', group: 'Preset' },
params: () => CommonStructureRepresentationParams,
async apply(ctx, state, structureCell, params, plugin) {
params: () => CommonStructureRepresentationPresetParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
if (!structureCell) return {};
const components = {
polymer: await presetStaticComponent(plugin, structureCell, 'polymer'),
ligand: await presetStaticComponent(plugin, structureCell, 'ligand'),
@@ -86,16 +105,19 @@ const polymerAndLigand = StructureRepresentationProvider({
coarse: builder.buildRepresentation(update, components.coarse, { type: 'spacefill', typeParams, color: color || 'polymer-id' })
};
await state.updateTree(update, { revertOnError: false }).runInContext(ctx);
await plugin.updateDataState(update, { revertOnError: false });
return { components, representations };
}
});
const proteinAndNucleic = StructureRepresentationProvider({
const proteinAndNucleic = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-protein-and-nucleic',
display: { name: 'Protein & Nucleic', group: 'Preset' },
params: () => CommonStructureRepresentationParams,
async apply(ctx, state, structureCell, params, plugin) {
params: () => CommonStructureRepresentationPresetParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
if (!structureCell) return {};
const components = {
protein: await presetSelectionComponent(plugin, structureCell, 'protein'),
nucleic: await presetSelectionComponent(plugin, structureCell, 'nucleic'),
@@ -107,19 +129,22 @@ const proteinAndNucleic = StructureRepresentationProvider({
nucleic: builder.buildRepresentation(update, components.nucleic, { type: 'gaussian-surface', typeParams, color })
};
await state.updateTree(update, { revertOnError: true }).runInContext(ctx);
await plugin.updateDataState(update, { revertOnError: true });
return { components, representations };
}
});
const coarseSurface = StructureRepresentationProvider({
const coarseSurface = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-coarse-surface',
display: { name: 'Coarse Surface', group: 'Preset' },
params: () => CommonStructureRepresentationParams,
async apply(ctx, state, structureCell, params, plugin) {
params: () => CommonStructureRepresentationPresetParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
if (!structureCell) return {};
const structure = structureCell.obj!.data;
const size = Structure.getSize(structure)
const gaussianProps = Object.create(null);
const components = Object.create(null);
@@ -139,22 +164,25 @@ const coarseSurface = StructureRepresentationProvider({
components.trace = await presetSelectionComponent(plugin, structureCell, 'polymer')
}
const { update, builder, typeParams, color } = reprBuilder(plugin, params);
const representations = {
trace: builder.buildRepresentation(update, components.trace, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color })
};
await state.updateTree(update, { revertOnError: true }).runInContext(ctx);
await plugin.updateDataState(update, { revertOnError: true });
return { components, representations };
}
});
const polymerCartoon = StructureRepresentationProvider({
const polymerCartoon = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-polymer-cartoon',
display: { name: 'Polymer Cartoon', group: 'Preset' },
params: () => CommonStructureRepresentationParams,
async apply(ctx, state, structureCell, params, plugin) {
params: () => CommonStructureRepresentationPresetParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
if (!structureCell) return {};
const components = {
polymer: await presetSelectionComponent(plugin, structureCell, 'polymer'),
};
@@ -164,16 +192,18 @@ const polymerCartoon = StructureRepresentationProvider({
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams, color })
};
await state.updateTree(update, { revertOnError: true }).runInContext(ctx);
await plugin.updateDataState(update, { revertOnError: true });
return { components, representations };
}
});
const atomicDetail = StructureRepresentationProvider({
const atomicDetail = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-atomic-detail',
display: { name: 'Atomic Detail', group: 'Preset' },
params: () => CommonStructureRepresentationParams,
async apply(ctx, state, structureCell, params, plugin) {
params: () => CommonStructureRepresentationPresetParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
if (!structureCell) return {};
const components = {
all: await presetSelectionComponent(plugin, structureCell, 'all'),
@@ -184,35 +214,26 @@ const atomicDetail = StructureRepresentationProvider({
all: builder.buildRepresentation(update, components.all, { type: 'ball-and-stick', typeParams, color })
};
await state.updateTree(update, { revertOnError: true }).runInContext(ctx);
await plugin.updateDataState(update, { revertOnError: true });
return { components, representations };
}
});
export function presetStaticComponent(plugin: PluginContext, structure: StateObjectRef<PluginStateObject.Molecule.Structure>, type: StaticStructureComponentType) {
return plugin.builders.structure.tryCreateStaticComponent({
structure,
type,
key: `static-${type}`,
tags: [RepresentationProviderTags.Component]
});
return plugin.builders.structure.tryCreateComponentStatic(structure, type, `static-${type}`);
}
export function presetSelectionComponent(plugin: PluginContext, structure: StateObjectRef<PluginStateObject.Molecule.Structure>, query: keyof typeof Q) {
return plugin.builders.structure.tryCreateQueryComponent({
structure,
query: Q[query],
key: `selection-${query}`,
tags: [RepresentationProviderTags.Component]
});
return plugin.builders.structure.tryCreateComponentFromSelection(structure, Q[query], `selection-${query}`);
}
export const PresetStructureReprentations = {
empty,
auto,
atomicDetail,
polymerCartoon,
polymerAndLigand,
proteinAndNucleic,
coarseSurface
'atomic-detail': atomicDetail,
'polymer-cartoon': polymerCartoon,
'polymer-and-ligand': polymerAndLigand,
'protein-and-nucleic': proteinAndNucleic,
'coarse-surface': coarseSurface
};
export type PresetStructureReprentations = typeof PresetStructureReprentations;

View File

@@ -5,7 +5,6 @@
*/
import { arrayFind } from '../../../mol-data/util';
import { Structure } from '../../../mol-model/structure';
import { PluginContext } from '../../../mol-plugin/context';
import { StateBuilder, StateObjectRef, StateObjectSelector } from '../../../mol-state';
import { Task } from '../../../mol-task';
@@ -15,34 +14,37 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { createStructureRepresentationParams, StructureRepresentationBuiltInProps, StructureRepresentationProps } from '../../helpers/structure-representation-params';
import { PluginStateObject } from '../../objects';
import { StructureRepresentation3D } from '../../transforms/representation';
import { PresetStructureReprentations } from './preset';
import { RepresentationProviderTags, StructureRepresentationProvider } from './provider';
import { PresetStructureReprentations, StructureRepresentationPresetProvider } from './representation-preset';
export type StructureRepresentationProviderRef = keyof PresetStructureReprentations | StructureRepresentationProvider | string
export type StructureRepresentationPresetProviderRef = keyof PresetStructureReprentations | StructureRepresentationPresetProvider | string
export const enum StructureRepresentationBuilderTags {
Representation = 'structure-representation'
}
export class StructureRepresentationBuilder {
private _providers: StructureRepresentationProvider[] = [];
private providerMap: Map<string, StructureRepresentationProvider> = new Map();
private _providers: StructureRepresentationPresetProvider[] = [];
private providerMap: Map<string, StructureRepresentationPresetProvider> = new Map();
private get dataState() { return this.plugin.state.data; }
readonly defaultProvider = PresetStructureReprentations.auto;
private resolveProvider(ref: StructureRepresentationProviderRef) {
private resolveProvider(ref: StructureRepresentationPresetProviderRef) {
return typeof ref === 'string'
? PresetStructureReprentations[ref as keyof PresetStructureReprentations] ?? arrayFind(this._providers, p => p.id === ref)
: ref;
}
hasPreset(s: Structure) {
hasPreset(s: PluginStateObject.Molecule.Structure) {
for (const p of this._providers) {
if (!p.isApplicable || p.isApplicable(s, this.plugin)) return true;
}
return false;
}
get providers(): ReadonlyArray<StructureRepresentationProvider> { return this._providers; }
get providers(): ReadonlyArray<StructureRepresentationPresetProvider> { return this._providers; }
getPresets(s?: Structure) {
getPresets(s?: PluginStateObject.Molecule.Structure) {
if (!s) return this.providers;
const ret = [];
for (const p of this._providers) {
@@ -52,7 +54,16 @@ export class StructureRepresentationBuilder {
return ret;
}
getPresetsWithOptions(s: Structure) {
getPresetSelect(s?: PluginStateObject.Molecule.Structure): PD.Select<string> {
const options: [string, string][] = [];
for (const p of this._providers) {
if (s && p.isApplicable && !p.isApplicable(s, this.plugin)) continue;
options.push([p.id, p.display.name]);
}
return PD.Select('auto', options);
}
getPresetsWithOptions(s: PluginStateObject.Molecule.Structure) {
const options: [string, string][] = [];
const map: { [K in string]: PD.Any } = Object.create(null);
for (const p of this._providers) {
@@ -65,7 +76,7 @@ export class StructureRepresentationBuilder {
return PD.MappedStatic(options[0][0], map, { options });
}
registerPreset(provider: StructureRepresentationProvider) {
registerPreset(provider: StructureRepresentationPresetProvider) {
if (this.providerMap.has(provider.id)) {
throw new Error(`Repr. provider with id '${provider.id}' already registered.`);
}
@@ -73,10 +84,10 @@ export class StructureRepresentationBuilder {
this.providerMap.set(provider.id, provider);
}
applyPreset<K extends keyof PresetStructureReprentations>(parent: StateObjectRef, preset: K, params?: StructureRepresentationProvider.Params<PresetStructureReprentations[K]>): Promise<StructureRepresentationProvider.State<PresetStructureReprentations[K]>> | undefined
applyPreset<P = any, S = {}>(parent: StateObjectRef, providers: StructureRepresentationProvider<P, S>, params?: P): Promise<S> | undefined
applyPreset(parent: StateObjectRef, providerId: string, params?: any): Promise<any> | undefined
applyPreset(parent: StateObjectRef, providerRef: string | StructureRepresentationProvider, params?: any): Promise<any> | undefined {
applyPreset<K extends keyof PresetStructureReprentations>(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, preset: K, params?: StructureRepresentationPresetProvider.Params<PresetStructureReprentations[K]>): Promise<StructureRepresentationPresetProvider.State<PresetStructureReprentations[K]>> | undefined
applyPreset<P = any, S = {}>(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, provider: StructureRepresentationPresetProvider<P, S>, params?: P): Promise<S> | undefined
applyPreset(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, providerId: string, params?: any): Promise<any> | undefined
applyPreset(parent: StateObjectRef, providerRef: string | StructureRepresentationPresetProvider, params?: any): Promise<any> | undefined {
const provider = this.resolveProvider(providerRef);
if (!provider) return;
@@ -88,11 +99,11 @@ export class StructureRepresentationBuilder {
}
const prms = params || (provider.params
? PD.getDefaultValues(provider.params(cell.obj!.data, this.plugin) as PD.Params)
? PD.getDefaultValues(provider.params(cell.obj, this.plugin) as PD.Params)
: {})
const task = Task.create(`${provider.display.name}`, ctx => provider.apply(ctx, state, cell, prms, this.plugin) as Promise<any>);
const task = Task.create(`${provider.display.name}`, () => provider.apply(cell, prms, this.plugin) as Promise<any>);
return this.plugin.runTask(task);
}
@@ -105,7 +116,7 @@ export class StructureRepresentationBuilder {
const params = createStructureRepresentationParams(this.plugin, data, props);
const repr = this.dataState.build()
.to(structure)
.apply(StructureRepresentation3D, params, { tags: RepresentationProviderTags.Representation });
.apply(StructureRepresentation3D, params, { tags: StructureRepresentationBuilderTags.Representation });
await this.plugin.updateDataState(repr);
return repr.selector;
@@ -121,7 +132,7 @@ export class StructureRepresentationBuilder {
const params = createStructureRepresentationParams(this.plugin, data, props);
return builder
.to(structure)
.apply(StructureRepresentation3D, params, { tags: RepresentationProviderTags.Representation })
.apply(StructureRepresentation3D, params, { tags: StructureRepresentationBuilderTags.Representation })
.selector;
}

View File

@@ -1,92 +0,0 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Model, Symmetry } from '../../mol-model/structure';
import { ShapeRepresentation } from '../../mol-repr/shape/representation';
import { Shape } from '../../mol-model/shape';
import { ColorNames } from '../../mol-util/color/names';
import { RuntimeContext } from '../../mol-task';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { BoxCage } from '../../mol-geo/primitive/box';
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
import { transformCage, cloneCage } from '../../mol-geo/primitive/cage';
import { radToDeg } from '../../mol-math/misc';
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
const translate05 = Mat4.fromTranslation(Mat4(), Vec3.create(0.5, 0.5, 0.5))
const unitCage = transformCage(cloneCage(BoxCage()), translate05)
const tmpRef = Vec3()
const tmpTranslate = Mat4()
interface UnitcellData {
symmetry: Symmetry
ref: Vec3
}
export const UnitcellParams = {
...Mesh.Params,
cellColor: PD.Color(ColorNames.orange),
cellScale: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 })
}
export type UnitcellParams = typeof UnitcellParams
export type UnitcellProps = PD.Values<UnitcellParams>
function getUnitcellMesh(data: UnitcellData, props: UnitcellProps, mesh?: Mesh) {
const state = MeshBuilder.createState(256, 128, mesh)
const { fromFractional } = data.symmetry.spacegroup.cell
Vec3.floor(tmpRef, data.ref)
Mat4.fromTranslation(tmpTranslate, tmpRef)
const cellCage = transformCage(cloneCage(unitCage), tmpTranslate)
const radius = (Math.cbrt(data.symmetry.spacegroup.cell.volume) / 300) * props.cellScale
state.currentGroup = 1
MeshBuilder.addCage(state, fromFractional, cellCage, radius, 2, 20)
return MeshBuilder.getMesh(state)
}
export async function getUnitcellRepresentation(ctx: RuntimeContext, model: Model, params: UnitcellProps, prev?: ShapeRepresentation<UnitcellData, Mesh, Mesh.Params>) {
const repr = prev || ShapeRepresentation(getUnitcellShape, Mesh.Utils);
const symmetry = ModelSymmetry.Provider.get(model)
if (symmetry) {
const data = {
symmetry,
ref: Vec3.transformMat4(Vec3(), Model.getCenter(model), symmetry.spacegroup.cell.toFractional)
}
await repr.createOrUpdate(params, data).runInContext(ctx);
}
return repr;
}
function getUnitcellLabel(data: UnitcellData) {
const { cell, name, num } = data.symmetry.spacegroup
const { size, anglesInRadians } = cell
const a = size[0].toFixed(2)
const b = size[1].toFixed(2)
const c = size[2].toFixed(2)
const alpha = radToDeg(anglesInRadians[0]).toFixed(2)
const beta = radToDeg(anglesInRadians[1]).toFixed(2)
const gamma = radToDeg(anglesInRadians[2]).toFixed(2)
const label: string[] = []
// name
label.push(`${name} #${num}`)
// sizes
label.push(`${a}\u00D7${b}\u00D7${c} \u212B`)
// angles
label.push(`\u03b1=${alpha}\u00B0 \u03b2=${beta}\u00B0 \u03b3=${gamma}\u00B0`)
return label.join(' | ')
}
function getUnitcellShape(ctx: RuntimeContext, data: UnitcellData, props: UnitcellProps, shape?: Shape<Mesh>) {
const geo = getUnitcellMesh(data, props, shape && shape.geometry);
const label = getUnitcellLabel(data)
return Shape.create('Unitcell', data, geo, () => props.cellColor, () => 1, () => label)
}

View File

@@ -17,7 +17,7 @@ import { PluginStateObject as SO } from '../objects';
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
export namespace RootStructureDefinition {
export function getParams(model?: Model, defaultValue?: 'deposited' | 'assembly' | 'symmetry' | 'symmetry-mates' | 'symmetry-assembly') {
export function getParams(model?: Model, defaultValue?: 'auto' | 'deposited' | 'assembly' | 'symmetry' | 'symmetry-mates' | 'symmetry-assembly') {
const symmetry = model && ModelSymmetry.Provider.get(model)
const assemblyIds = symmetry ? symmetry.assemblies.map(a => [a.id, `${a.id}: ${stringToWords(a.details)}`] as [string, string]) : [];
@@ -40,6 +40,7 @@ export namespace RootStructureDefinition {
}
const modes = {
auto: PD.EmptyGroup(),
deposited: PD.EmptyGroup(),
assembly: PD.Group({
id: PD.Optional(model
@@ -68,11 +69,15 @@ export namespace RootStructureDefinition {
}, { isFlat: true })
};
const options: [keyof typeof modes, string][] = [
['deposited', 'Deposited']
];
const options: [keyof typeof modes, string][] = [];
if (assemblyIds) {
if (defaultValue === 'auto') {
options.push(['auto', 'Auto']);
}
options.push(['deposited', 'Deposited']);
if (assemblyIds.length > 0) {
options.push(['assembly', 'Assembly']);
}
@@ -149,6 +154,14 @@ export namespace RootStructureDefinition {
const s = Structure.ofModel(model);
return new SO.Molecule.Structure(s, { label: 'Deposited', description: Structure.elementDescription(s) });
}
if (params.name === 'auto') {
if (symmetry.assemblies.length === 0) {
const s = Structure.ofModel(model);
return new SO.Molecule.Structure(s, { label: 'Deposited', description: Structure.elementDescription(s) });
} else {
return buildAssembly(plugin, ctx, model);
}
}
if (params.name === 'assembly') {
return buildAssembly(plugin, ctx, model, params.params.id)
}

View File

@@ -115,7 +115,7 @@ export function updateStructureComponent(a: Structure, b: SO.Molecule.Structure,
return StateTransformer.UpdateResult.Recreate;
}
if (a !== cache.source) return StateTransformer.UpdateResult.Unchanged;
if (a === cache.source) return StateTransformer.UpdateResult.Unchanged;
const entry = (cache as { entry: StructureQueryHelper.CacheEntry }).entry;

View File

@@ -41,12 +41,8 @@ export interface StructureRepresentationProps<
sizeParams?: Partial<SizeTheme.ParamValues<S>>
}
export function createStructureRepresentationParams
<R extends StructureRepresentationRegistry.BuiltIn, C extends ColorTheme.BuiltIn, S extends SizeTheme.BuiltIn>
(ctx: PluginContext, structure?: Structure, props?: StructureRepresentationBuiltInProps<R, C, S>): StateTransformer.Params<StructureRepresentation3D>
export function createStructureRepresentationParams
<R extends RepresentationProvider<Structure>, C extends ColorTheme.Provider, S extends SizeTheme.Provider>
(ctx: PluginContext, structure?: Structure, props?: StructureRepresentationProps<R, C, S>): StateTransformer.Params<StructureRepresentation3D>
export function createStructureRepresentationParams<R extends StructureRepresentationRegistry.BuiltIn, C extends ColorTheme.BuiltIn, S extends SizeTheme.BuiltIn>(ctx: PluginContext, structure?: Structure, props?: StructureRepresentationBuiltInProps<R, C, S>): StateTransformer.Params<StructureRepresentation3D>
export function createStructureRepresentationParams<R extends RepresentationProvider<Structure>, C extends ColorTheme.Provider, S extends SizeTheme.Provider>(ctx: PluginContext, structure?: Structure, props?: StructureRepresentationProps<R, C, S>): StateTransformer.Params<StructureRepresentation3D>
export function createStructureRepresentationParams(ctx: PluginContext, structure?: Structure, props: any = {}): StateTransformer.Params<StructureRepresentation3D> {
const p = props as StructureRepresentationBuiltInProps;
if (typeof p.type === 'string' || typeof p.color === 'string' || typeof p.size === 'string') return createParamsByName(ctx, structure || Structure.Empty, props);
@@ -59,8 +55,7 @@ export function getStructureThemeTypes(ctx: PluginContext, structure?: Structure
return themeCtx.colorThemeRegistry.getApplicableTypes({ structure });
}
export function createStructureColorThemeParams<T extends ColorTheme.BuiltIn>
(ctx: PluginContext, structure: Structure | undefined, typeName: string | undefined, themeName: T, params?: ColorTheme.BuiltInParams<T>): StateTransformer.Params<StructureRepresentation3D>['colorTheme']
export function createStructureColorThemeParams<T extends ColorTheme.BuiltIn>(ctx: PluginContext, structure: Structure | undefined, typeName: string | undefined, themeName: T, params?: ColorTheme.BuiltInParams<T>): StateTransformer.Params<StructureRepresentation3D>['colorTheme']
export function createStructureColorThemeParams(ctx: PluginContext, structure: Structure | undefined, typeName: string | undefined, themeName?: string, params?: any): StateTransformer.Params<StructureRepresentation3D>['colorTheme']
export function createStructureColorThemeParams(ctx: PluginContext, structure: Structure | undefined, typeName: string | undefined, themeName?: string, params?: any): StateTransformer.Params<StructureRepresentation3D>['colorTheme'] {
const { registry, themes } = ctx.representation.structure;
@@ -71,8 +66,7 @@ export function createStructureColorThemeParams(ctx: PluginContext, structure: S
return { name: color.name, params: Object.assign(colorDefaultParams, params) };
}
export function createStructureSizeThemeParams<T extends SizeTheme.BuiltIn>
(ctx: PluginContext, structure: Structure | undefined, typeName: string | undefined, themeName: T, params?: SizeTheme.BuiltInParams<T>): StateTransformer.Params<StructureRepresentation3D>['sizeTheme']
export function createStructureSizeThemeParams<T extends SizeTheme.BuiltIn>(ctx: PluginContext, structure: Structure | undefined, typeName: string | undefined, themeName: T, params?: SizeTheme.BuiltInParams<T>): StateTransformer.Params<StructureRepresentation3D>['sizeTheme']
export function createStructureSizeThemeParams(ctx: PluginContext, structure: Structure | undefined, typeName: string | undefined, themeName?: string, params?: any): StateTransformer.Params<StructureRepresentation3D>['sizeTheme']
export function createStructureSizeThemeParams(ctx: PluginContext, structure: Structure | undefined, typeName: string | undefined, themeName?: string, params?: any): StateTransformer.Params<StructureRepresentation3D>['sizeTheme'] {
const { registry, themes } = ctx.representation.structure;
@@ -86,7 +80,7 @@ export function createStructureSizeThemeParams(ctx: PluginContext, structure: St
function createParamsByName(ctx: PluginContext, structure: Structure, props: StructureRepresentationBuiltInProps): StateTransformer.Params<StructureRepresentation3D> {
const typeProvider = (props.type && ctx.representation.structure.registry.get(props.type))
|| ctx.representation.structure.registry.default.provider;
const colorProvider = (props.color && ctx.representation.structure.themes.colorThemeRegistry.get(props.color))
const colorProvider = (props.color && ctx.representation.structure.themes.colorThemeRegistry.get(props.color))
|| ctx.representation.structure.themes.colorThemeRegistry.get(typeProvider.defaultColorTheme.name);
const sizeProvider = (props.size && ctx.representation.structure.themes.sizeThemeRegistry.get(props.size))
|| ctx.representation.structure.themes.sizeThemeRegistry.get(typeProvider.defaultSizeTheme.name);
@@ -104,11 +98,11 @@ function createParamsByName(ctx: PluginContext, structure: Structure, props: Str
function createParamsProvider(ctx: PluginContext, structure: Structure, props: StructureRepresentationProps = {}): StateTransformer.Params<StructureRepresentation3D> {
const { themes: themeCtx } = ctx.representation.structure
const themeDataCtx = { structure };
const repr = props.type || ctx.representation.structure.registry.default.provider;
const reprDefaultParams = PD.getDefaultValues(repr.getParams(themeCtx, structure));
const reprParams = Object.assign(reprDefaultParams, props.typeParams);
const color = props.color || themeCtx.colorThemeRegistry.get(repr.defaultColorTheme.name);
const colorDefaultParams = PD.getDefaultValues(color.getParams(themeDataCtx));
if (color.name === repr.defaultColorTheme.name) Object.assign(colorDefaultParams, repr.defaultColorTheme.props);

View File

@@ -77,7 +77,7 @@ function StructureSelectionQuery(label: string, expression: Expression, props: S
if (props.ensureCustomProperties) {
await props.ensureCustomProperties({ fetch: plugin.fetch, runtime }, structure)
}
if (!_query) _query = compile<StructureSelection>(expression)
if (!_query) _query = compile<StructureSelection>(expression)
return _query(new QueryContext(structure, { currentSelection }));
}
}
@@ -188,7 +188,7 @@ const helix = StructureSelectionQuery('Helix', MS.struct.modifier.union([
MS.core.type.bitflags([SecondaryStructureType.Flag.Helix])
])
})
]), { category: StructureSelectionCategory.Residue })
]), { category: StructureSelectionCategory.Structure })
const beta = StructureSelectionQuery('Beta Strand/Sheet', MS.struct.modifier.union([
MS.struct.generator.atomGroups({
@@ -204,7 +204,7 @@ const beta = StructureSelectionQuery('Beta Strand/Sheet', MS.struct.modifier.uni
MS.core.type.bitflags([SecondaryStructureType.Flag.Beta])
])
})
]), { category: StructureSelectionCategory.Residue })
]), { category: StructureSelectionCategory.Structure })
const water = StructureSelectionQuery('Water', MS.struct.modifier.union([
MS.struct.generator.atomGroups({

View File

@@ -1,14 +1,15 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Sphere3D } from '../../mol-math/geometry';
import { StructureElement } from '../../mol-model/structure';
import { PluginContext } from '../../mol-plugin/context';
import { PrincipalAxes } from '../../mol-math/linear-algebra/matrix/principal-axes';
import { Camera } from '../../mol-canvas3d/camera';
import { Loci } from '../../mol-model/loci';
// TODO: make this customizable somewhere?
const DefaultCameraFocusOptions = {
@@ -20,14 +21,16 @@ const DefaultCameraFocusOptions = {
export type CameraFocusOptions = typeof DefaultCameraFocusOptions
export class CameraManager {
focusLoci(loci: StructureElement.Loci, options?: Partial<CameraFocusOptions>) {
focusLoci(loci: Loci, options?: Partial<CameraFocusOptions>) {
// TODO: allow computation of principal axes here?
// perhaps have an optimized function, that does exact axes small Loci and approximate/sampled from big ones?
const { extraRadius, minRadius, durationMs } = { ...DefaultCameraFocusOptions, ...options };
const { sphere } = StructureElement.Loci.getBoundary(loci);
const radius = Math.max(sphere.radius + extraRadius, minRadius);
this.plugin.canvas3d?.camera.focus(sphere.center, radius, durationMs);
const sphere = Loci.getBoundingSphere(loci);
if (sphere) {
const radius = Math.max(sphere.radius + extraRadius, minRadius);
this.plugin.canvas3d?.camera.focus(sphere.center, radius, durationMs);
}
}
focusSphere(sphere: Sphere3D, options?: Partial<CameraFocusOptions> & { principalAxes?: PrincipalAxes }) {

View File

@@ -1,11 +1,11 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2020 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>
*/
import { EmptyLoci, EveryLoci, isEmptyLoci, Loci as ModelLoci } from '../../mol-model/loci';
import { EveryLoci, isEmptyLoci, Loci } from '../../mol-model/loci';
import { Structure, StructureElement } from '../../mol-model/structure';
import { PluginContext } from '../../mol-plugin/context';
import { Representation } from '../../mol-repr/representation';
@@ -63,25 +63,16 @@ class InteractivityManager extends PluginComponent<InteractivityManagerState> {
}
namespace InteractivityManager {
export interface Loci<T extends ModelLoci = ModelLoci> { loci: T, repr?: Representation.Any }
export namespace Loci {
export function areEqual(a: Loci, b: Loci) {
return a.repr === b.repr && ModelLoci.areEqual(a.loci, b.loci);
}
export const Empty: Loci = { loci: EmptyLoci };
}
export const Params = {
granularity: PD.Select('residue', ModelLoci.GranularityOptions, { label: 'Picking Level', description: 'Controls if selections are expanded upon picking to whole residues, chains, structures, instances, or left as atoms and coarse elements' }),
granularity: PD.Select('residue', Loci.GranularityOptions, { label: 'Picking Level', description: 'Controls if selections are expanded upon picking to whole residues, chains, structures, instances, or left as atoms and coarse elements' }),
}
export type Params = typeof Params
export type Props = PD.Values<Params>
export interface HoverEvent { current: Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys }
export interface ClickEvent { current: Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys }
export interface HoverEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys }
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys }
export type LociMarkProvider = (loci: Loci, action: MarkerAction) => void
export type LociMarkProvider = (loci: Representation.Loci, action: MarkerAction) => void
export abstract class LociMarkManager {
protected providers: LociMarkProvider[] = [];
@@ -102,13 +93,13 @@ namespace InteractivityManager {
// TODO clear, then re-apply remaining providers
}
protected normalizedLoci(interactivityLoci: Loci, applyGranularity = true) {
const { loci, repr } = interactivityLoci
protected normalizedLoci(reprLoci: Representation.Loci, applyGranularity = true) {
const { loci, repr } = reprLoci
const granularity = applyGranularity ? this.props.granularity : undefined
return { loci: ModelLoci.normalize(loci, granularity), repr }
return { loci: Loci.normalize(loci, granularity), repr }
}
protected mark(current: Loci<ModelLoci>, action: MarkerAction) {
protected mark(current: Representation.Loci, action: MarkerAction) {
for (let p of this.providers) p(current, action);
}
@@ -121,16 +112,16 @@ namespace InteractivityManager {
//
export class LociHighlightManager extends LociMarkManager {
private prev: Loci[] = [];
private prev: Representation.Loci[] = [];
private isHighlighted(loci: Loci) {
private isHighlighted(loci: Representation.Loci) {
for (const p of this.prev) {
if (Loci.areEqual(p, loci)) return true
if (Representation.Loci.areEqual(p, loci)) return true
}
return false
}
private addHighlight(loci: Loci) {
private addHighlight(loci: Representation.Loci) {
this.mark(loci, MarkerAction.Highlight);
this.prev.push(loci)
}
@@ -142,14 +133,14 @@ namespace InteractivityManager {
this.prev.length = 0
}
highlight(current: Loci, applyGranularity = true) {
highlight(current: Representation.Loci, applyGranularity = true) {
const normalized = this.normalizedLoci(current, applyGranularity)
if (!this.isHighlighted(normalized)) {
this.addHighlight(normalized)
}
}
highlightOnly(current: Loci, applyGranularity = true) {
highlightOnly(current: Representation.Loci, applyGranularity = true) {
const normalized = this.normalizedLoci(current, applyGranularity)
if (!this.isHighlighted(normalized)) {
this.clearHighlights()
@@ -157,7 +148,7 @@ namespace InteractivityManager {
}
}
highlightOnlyExtend(current: Loci, applyGranularity = true) {
highlightOnlyExtend(current: Representation.Loci, applyGranularity = true) {
const normalized = this.normalizedLoci(current, applyGranularity)
if (StructureElement.Loci.is(normalized.loci)) {
const loci = {
@@ -175,8 +166,8 @@ namespace InteractivityManager {
//
export class LociSelectManager extends LociMarkManager {
toggle(current: Loci<ModelLoci>, applyGranularity = true) {
if (ModelLoci.isEmpty(current.loci)) return;
toggle(current: Representation.Loci, applyGranularity = true) {
if (Loci.isEmpty(current.loci)) return;
const normalized = this.normalizedLoci(current, applyGranularity)
if (StructureElement.Loci.is(normalized.loci)) {
@@ -186,8 +177,8 @@ namespace InteractivityManager {
}
}
toggleExtend(current: Loci<ModelLoci>, applyGranularity = true) {
if (ModelLoci.isEmpty(current.loci)) return;
toggleExtend(current: Representation.Loci, applyGranularity = true) {
if (Loci.isEmpty(current.loci)) return;
const normalized = this.normalizedLoci(current, applyGranularity)
if (StructureElement.Loci.is(normalized.loci)) {
@@ -196,7 +187,7 @@ namespace InteractivityManager {
}
}
select(current: Loci<ModelLoci>, applyGranularity = true) {
select(current: Representation.Loci, applyGranularity = true) {
const normalized = this.normalizedLoci(current, applyGranularity)
if (StructureElement.Loci.is(normalized.loci)) {
this.sel.modify('add', normalized.loci);
@@ -204,7 +195,7 @@ namespace InteractivityManager {
this.mark(normalized, MarkerAction.Select);
}
selectJoin(current: Loci<ModelLoci>, applyGranularity = true) {
selectJoin(current: Representation.Loci, applyGranularity = true) {
const normalized = this.normalizedLoci(current, applyGranularity)
if (StructureElement.Loci.is(normalized.loci)) {
this.sel.modify('intersect', normalized.loci);
@@ -212,7 +203,7 @@ namespace InteractivityManager {
this.mark(normalized, MarkerAction.Select);
}
selectOnly(current: Loci<ModelLoci>, applyGranularity = true) {
selectOnly(current: Representation.Loci, applyGranularity = true) {
this.deselectAll()
const normalized = this.normalizedLoci(current, applyGranularity)
if (StructureElement.Loci.is(normalized.loci)) {
@@ -221,7 +212,7 @@ namespace InteractivityManager {
this.mark(normalized, MarkerAction.Select);
}
deselect(current: Loci<ModelLoci>, applyGranularity = true) {
deselect(current: Representation.Loci, applyGranularity = true) {
const normalized = this.normalizedLoci(current, applyGranularity)
if (StructureElement.Loci.is(normalized.loci)) {
this.sel.modify('remove', normalized.loci);
@@ -234,11 +225,11 @@ namespace InteractivityManager {
this.mark({ loci: EveryLoci }, MarkerAction.Deselect);
}
deselectAllOnEmpty(current: Loci<ModelLoci>) {
deselectAllOnEmpty(current: Representation.Loci) {
if (isEmptyLoci(current.loci)) this.deselectAll()
}
protected mark(current: Loci<ModelLoci>, action: MarkerAction.Select | MarkerAction.Deselect) {
protected mark(current: Representation.Loci, action: MarkerAction.Select | MarkerAction.Deselect) {
const { loci } = current
if (StructureElement.Loci.is(loci)) {
// do a full deselect/select for the current structure so visuals that are
@@ -251,7 +242,7 @@ namespace InteractivityManager {
}
}
private toggleSel(current: Loci<ModelLoci>) {
private toggleSel(current: Representation.Loci) {
if (this.sel.has(current.loci)) {
this.sel.modify('remove', current.loci);
this.mark(current, MarkerAction.Deselect);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -9,6 +9,7 @@ import { PluginContext } from '../../mol-plugin/context';
import { Loci } from '../../mol-model/loci';
import { Representation } from '../../mol-repr/representation';
import { MarkerAction } from '../../mol-util/marker-action';
import { arrayRemoveAtInPlace } from '../../mol-util/array';
export type LociLabelEntry = JSX.Element | string
export type LociLabelProvider = (info: Loci, repr?: Representation<any>) => LociLabelEntry | undefined
@@ -18,25 +19,55 @@ export class LociLabelManager {
addProvider(provider: LociLabelProvider) {
this.providers.push(provider);
this.isDirty = true
this.showLabels()
}
removeProvider(provider: LociLabelProvider) {
this.providers = this.providers.filter(p => p !== provider);
// Event.Interactivity.Highlight.dispatch(this.ctx, []);
this.isDirty = true
this.showLabels()
}
private empty: LociLabelEntry[] = [];
private getInfo({ loci, repr }: Representation.Loci, action: MarkerAction) {
if (Loci.isEmpty(loci) || action !== MarkerAction.Highlight) return this.empty;
const info: LociLabelEntry[] = [];
for (let p of this.providers) {
const e = p(loci, repr);
if (e) info.push(e);
private locis: Representation.Loci[] = []
private mark(loci: Representation.Loci, action: MarkerAction) {
const idx = this.locis.findIndex(l => Representation.Loci.areEqual(loci, l))
if (idx === -1 && action === MarkerAction.Highlight) {
this.locis.push(loci)
this.isDirty = true
} else if(idx !== -1 && action === MarkerAction.RemoveHighlight) {
arrayRemoveAtInPlace(this.locis, idx)
this.isDirty = true
}
return info;
}
private isDirty = false
private entries: LociLabelEntry[] = []
private showLabels() {
this.ctx.behaviors.labels.highlight.next({ entries: this.getEntries() })
}
private getEntries() {
if (this.isDirty) {
this.entries.length = 0
for (const provider of this.providers) {
for (const loci of this.locis) {
if (Loci.isEmpty(loci.loci)) continue
const entry = provider(loci.loci, loci.repr)
if (entry) this.entries.push(entry)
}
}
this.isDirty = false
}
return this.entries
}
constructor(public ctx: PluginContext) {
ctx.managers.interactivity.lociHighlights.addProvider((loci, action) => ctx.behaviors.labels.highlight.next({ entries: this.getInfo(loci, action) }))
ctx.managers.interactivity.lociHighlights.addProvider((loci, action) => {
this.mark(loci, action)
this.showLabels()
})
}
}

View File

@@ -13,10 +13,9 @@ import { PluginContext } from '../../../mol-plugin/context';
import { StateBuilder, StateTransformer } from '../../../mol-state';
import { Task } from '../../../mol-task';
import { UUID } from '../../../mol-util';
import { arraySetAdd } from '../../../mol-util/array';
import { ColorNames } from '../../../mol-util/color/names';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { StructureRepresentationProvider } from '../../builder/structure/provider';
import { StructureRepresentationPresetProvider } from '../../builder/structure/representation-preset';
import { PluginComponent } from '../../component';
import { StructureComponentParams } from '../../helpers/structure-component';
import { clearStructureOverpaint, setStructureOverpaint } from '../../helpers/structure-overpaint';
@@ -86,14 +85,13 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
private async updateInterationProps() {
for (const s of this.currentStructures) {
const interactionParams = InteractionsProvider.getParams(s.cell.obj?.data!);
if (s.properties) {
const params = s.properties.cell.transform.params;
if (PD.areEqual(interactionParams, params, this.state.options.interactions)) continue;
const oldParams = s.properties.cell.transform.params?.properties[InteractionsProvider.descriptor.name];
if (PD.areEqual(interactionParams, oldParams, this.state.options.interactions)) continue;
const b = this.dataState.build();
b.to(s.properties.cell).update(old => {
arraySetAdd(old.autoAttach, InteractionsProvider.descriptor.name);
old.properties[InteractionsProvider.descriptor.name] = this.state.options.interactions;
});
await this.plugin.updateDataState(b);
@@ -101,15 +99,13 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
const pd = this.plugin.customStructureProperties.getParams(s.cell.obj?.data);
const params = PD.getDefaultValues(pd);
if (PD.areEqual(interactionParams, params.properties[InteractionsProvider.descriptor.name], this.state.options.interactions)) continue;
arraySetAdd(params.autoAttach, InteractionsProvider.descriptor.name);
params.properties[InteractionsProvider.descriptor.name] = this.state.options.interactions;
await this.plugin.builders.structure.insertStructureProperties(s.cell, params);
}
}
}
applyPreset<P = any, S = {}>(structures: ReadonlyArray<StructureRef>, provider: StructureRepresentationProvider<P, S>, params?: P): Promise<any> {
applyPreset<P = any, S = {}>(structures: ReadonlyArray<StructureRef>, provider: StructureRepresentationPresetProvider<P, S>, params?: P): Promise<any> {
return this.plugin.dataTransaction(async () => {
await this.clearComponents(structures);
for (const s of structures) {
@@ -137,12 +133,12 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
modifyByCurrentSelection(components: ReadonlyArray<StructureComponentRef>, action: StructureComponentManager.ModifyAction) {
return this.plugin.runTask(Task.create('Modify Component', async taskCtx => {
const b = this.dataState.build();
const b = this.dataState.build();
for (const c of components) {
if (!this.canBeModified(c)) continue;
const selection = this.plugin.managers.structure.selection.getStructure(c.structure.cell.obj!.data);
if (!selection || selection.elementCount === 0) continue;
if (!selection || selection.elementCount === 0) continue;
this.modifyComponent(b, c, selection, action);
}
await this.dataState.updateTree(b, { canUndo: 'Modify Selection' }).runInContext(taskCtx);
@@ -157,7 +153,7 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
for (const c of components) {
setSubtreeVisibility(this.dataState, c.cell.transform.ref, isHidden);
}
} else {
} else {
const index = components[0].representations.indexOf(reprPivot);
const isHidden = !reprPivot.cell.state.isHidden;
@@ -192,14 +188,14 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
return this.plugin.managers.structure.hierarchy.remove(toRemove, true);
}
updateRepresentations(components: ReadonlyArray<StructureComponentRef>, pivot: StructureRepresentationRef, params: StateTransformer.Params<StructureRepresentation3D>) {
updateRepresentations(components: ReadonlyArray<StructureComponentRef>, pivot: StructureRepresentationRef, params: StateTransformer.Params<StructureRepresentation3D>) {
if (components.length === 0) return Promise.resolve();
const index = components[0].representations.indexOf(pivot);
if (index < 0) return Promise.resolve();
const update = this.dataState.build();
for (const c of components) {
// TODO: is it ok to use just the index here? Could possible lead to ugly edge cases, but perhaps not worth the trouble to "fix".
const repr = c.representations[index];
@@ -212,24 +208,41 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
return this.plugin.updateDataState(update, { canUndo: 'Update Representation' });
}
updateRepresentationsTheme<C extends ColorTheme.BuiltIn, S extends SizeTheme.BuiltIn>(components: ReadonlyArray<StructureComponentRef>, params: StructureComponentManager.UpdateThemeParams<C, S>): Promise<any> {
if (components.length === 0) return Promise.resolve();
/**
* To update theme for all selected structures, use
* plugin.dataTransaction(async () => {
* for (const s of structure.hierarchy.selection.structures) await updateRepresentationsTheme(s.componets, ...);
* }, { canUndo: 'Update Theme' });
*/
updateRepresentationsTheme<C extends ColorTheme.BuiltIn, S extends SizeTheme.BuiltIn>(components: ReadonlyArray<StructureComponentRef>, params: StructureComponentManager.UpdateThemeParams<C, S>): Promise<any> | undefined
updateRepresentationsTheme<C extends ColorTheme.BuiltIn, S extends SizeTheme.BuiltIn>(components: ReadonlyArray<StructureComponentRef>, params: (c: StructureComponentRef, r: StructureRepresentationRef) => StructureComponentManager.UpdateThemeParams<C, S>): Promise<any> | undefined
updateRepresentationsTheme(components: ReadonlyArray<StructureComponentRef>, paramsOrProvider: StructureComponentManager.UpdateThemeParams<any, any> | ((c: StructureComponentRef, r: StructureRepresentationRef) => StructureComponentManager.UpdateThemeParams<any, any>)) {
if (components.length === 0) return;
const update = this.dataState.build();
for (const c of components) {
for (const repr of c.representations) {
const old = repr.cell.transform.params;
const colorTheme = params.color
? createStructureColorThemeParams(this.plugin, c.structure.cell.obj?.data, old?.type.name, params.color, params.colorParams)
: void 0;
const sizeTheme = params.color
? createStructureSizeThemeParams(this.plugin, c.structure.cell.obj?.data, old?.type.name, params.size, params.sizeParams)
: void 0;
update.to(repr.cell).update(prev => {
if (colorTheme) prev.colorTheme = colorTheme;
if (sizeTheme) prev.sizeTheme = sizeTheme;
});
const params: StructureComponentManager.UpdateThemeParams<any, any> = typeof paramsOrProvider === 'function' ? paramsOrProvider(c, repr) : paramsOrProvider;
const colorTheme = params.color === 'default'
? createStructureColorThemeParams(this.plugin, c.structure.cell.obj?.data, old?.type.name)
: params.color
? createStructureColorThemeParams(this.plugin, c.structure.cell.obj?.data, old?.type.name, params.color, params.colorParams)
: void 0;
const sizeTheme = params.size === 'default'
? createStructureSizeThemeParams(this.plugin, c.structure.cell.obj?.data, old?.type.name)
: params.color
? createStructureSizeThemeParams(this.plugin, c.structure.cell.obj?.data, old?.type.name, params.size, params.sizeParams)
: void 0;
if (colorTheme || sizeTheme) {
update.to(repr.cell).update(prev => {
if (colorTheme) prev.colorTheme = colorTheme;
if (sizeTheme) prev.sizeTheme = sizeTheme;
});
}
}
}
@@ -260,10 +273,7 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
const componentKey = UUID.create22();
for (const s of xs) {
const component = await this.plugin.builders.structure.tryCreateQueryComponent({
structure: s.childRoot,
query: params.selection,
key: componentKey,
const component = await this.plugin.builders.structure.tryCreateComponentFromSelection(s.childRoot, params.selection, componentKey, {
label: params.label || (params.selection === StructureSelectionQueries.current ? 'Custom Selection' : ''),
});
if (params.representation === 'none' || !component) continue;
@@ -297,11 +307,11 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
if ((action === 'subtract' || action === 'intersect') && !structureAreIntersecting(structure, by)) return;
const parent = component.structure.cell.obj?.data!;
const modified = action === 'union'
? structureUnion(parent, [structure, by])
const modified = action === 'union'
? structureUnion(parent, [structure, by])
: action === 'intersect'
? structureIntersect(structure, by)
: structureSubtract(structure, by);
? structureIntersect(structure, by)
: structureSubtract(structure, by);
if (modified.elementCount === 0) {
builder.delete(component.cell.transform.ref);
@@ -346,9 +356,9 @@ namespace StructureComponentManager {
interactions: PD.Group(InteractionsProvider.defaultParams, { label: 'Non-covalent Interactions' }),
}
export type Options = PD.Values<typeof OptionsParams>
const SelectionParam = PD.Select(StructureSelectionQueryOptions[1][0], StructureSelectionQueryOptions)
export function getAddParams(plugin: PluginContext) {
return {
selection: SelectionParam,
@@ -389,12 +399,12 @@ namespace StructureComponentManager {
export type ModifyAction = 'union' | 'subtract' | 'intersect'
export interface UpdateThemeParams<C extends ColorTheme.BuiltIn, S extends SizeTheme.BuiltIn> {
/**
/**
* this works for any theme name (use 'name as any'), but code completion will break
*/
color?: C,
color?: C | 'default',
colorParams?: ColorTheme.BuiltInParams<C>,
size?: S,
size?: S | 'default',
sizeParams?: SizeTheme.BuiltInParams<S>
}
}

View File

@@ -7,7 +7,7 @@
import { PluginStateObject as SO } from '../../objects';
import { StateObject, StateTransform, State, StateObjectCell, StateTree, StateTransformer } from '../../../mol-state';
import { StructureBuilderTags } from '../../builder/structure';
import { RepresentationProviderTags } from '../../builder/structure/provider';
import { StructureRepresentationBuilderTags } from '../../builder/structure/representation';
import { StructureRepresentationInteractionTags } from '../../../mol-plugin/behavior/dynamic/selection/structure-representation-interaction';
import { StateTransforms } from '../../transforms';
import { VolumeStreaming } from '../../../mol-plugin/behavior/dynamic/volume-streaming/behavior';
@@ -15,10 +15,9 @@ import { CreateVolumeStreamingBehavior } from '../../../mol-plugin/behavior/dyna
export function buildStructureHierarchy(state: State, previous?: StructureHierarchy) {
const build = BuildState(state, previous || StructureHierarchy());
// StateTree.doPreOrder(state.tree, state.tree.root, build, visitCell);
doPreOrder(state.tree, build);
if (previous) previous.refs.forEach(isRemoved, build);
return { hierarchy: build.hierarchy, added: build.added, updated: build.updated, removed: build.removed };
return { hierarchy: build.hierarchy, added: build.added, changed: build.changed };
}
export interface StructureHierarchy {
@@ -38,9 +37,9 @@ interface RefBase<K extends string = string, O extends StateObject = StateObject
version: StateTransform['version']
}
export type HierarchyRef =
export type HierarchyRef =
| TrajectoryRef
| ModelRef | ModelPropertiesRef
| ModelRef | ModelPropertiesRef | ModelUnitcellRef
| StructureRef | StructurePropertiesRef | StructureVolumeStreamingRef | StructureComponentRef | StructureRepresentationRef
| GenericRepresentationRef
@@ -48,7 +47,7 @@ export interface TrajectoryRef extends RefBase<'trajectory', SO.Molecule.Traject
models: ModelRef[]
}
function TrajectoryRef(cell: StateObjectCell<SO.Molecule.Trajectory>): TrajectoryRef {
function TrajectoryRef(cell: StateObjectCell<SO.Molecule.Trajectory>): TrajectoryRef {
return { kind: 'trajectory', cell, version: cell.transform.version, models: [] };
}
@@ -57,6 +56,7 @@ export interface ModelRef extends RefBase<'model', SO.Molecule.Model> {
properties?: ModelPropertiesRef,
structures: StructureRef[],
genericRepresentations?: GenericRepresentationRef[],
unitcell?: ModelUnitcellRef,
/** to support decorators */
childRoot: StateObjectCell<SO.Molecule.Model>
}
@@ -73,6 +73,14 @@ function ModelPropertiesRef(cell: StateObjectCell<SO.Molecule.Model>, model: Mod
return { kind: 'model-properties', cell, version: cell.transform.version, model };
}
export interface ModelUnitcellRef extends RefBase<'model-unitcell', SO.Shape.Representation3D, StateTransforms['Representation']['ModelUnitcell3D']> {
model: ModelRef
}
function ModelUnitcellRef(cell: StateObjectCell<SO.Shape.Representation3D>, model: ModelRef): ModelUnitcellRef {
return { kind: 'model-unitcell', cell, version: cell.transform.version, model };
}
export interface StructureRef extends RefBase<'structure', SO.Molecule.Structure> {
model?: ModelRef,
properties?: StructurePropertiesRef,
@@ -150,13 +158,12 @@ interface BuildState {
currentStructure?: StructureRef,
currentComponent?: StructureComponentRef,
updated: HierarchyRef[],
added: HierarchyRef[],
removed: HierarchyRef[]
changed: boolean,
added: Set<StateTransform.Ref>
}
function BuildState(state: State, oldHierarchy: StructureHierarchy): BuildState {
return { state, oldHierarchy, hierarchy: StructureHierarchy(), updated: [], added: [], removed: [] };
return { state, oldHierarchy, hierarchy: StructureHierarchy(), changed: false, added: new Set() };
}
function createOrUpdateRefList<R extends HierarchyRef, C extends any[]>(state: BuildState, cell: StateObjectCell, list: R[], ctor: (...args: C) => R, ...args: C) {
@@ -165,9 +172,10 @@ function createOrUpdateRefList<R extends HierarchyRef, C extends any[]>(state: B
state.hierarchy.refs.set(cell.transform.ref, ref);
const old = state.oldHierarchy.refs.get(cell.transform.ref);
if (old) {
if (old.version !== cell.transform.version) state.updated.push(ref);
if (old.version !== cell.transform.version) state.changed = true;
} else {
state.added.push(ref);
state.added.add(ref.cell.transform.ref);
state.changed = true;
}
return ref;
}
@@ -177,9 +185,10 @@ function createOrUpdateRef<R extends HierarchyRef, C extends any[]>(state: Build
state.hierarchy.refs.set(cell.transform.ref, ref);
const old = state.oldHierarchy.refs.get(cell.transform.ref);
if (old) {
if (old.version !== cell.transform.version) state.updated.push(ref);
if (old.version !== cell.transform.version) state.changed = true;
} else {
state.added.push(ref);
state.added.add(ref.cell.transform.ref);
state.changed = true;
}
return ref;
}
@@ -200,6 +209,15 @@ const tagMap: [string, (state: BuildState, cell: StateObjectCell) => boolean | v
if (!state.currentModel) return false;
state.currentModel.properties = createOrUpdateRef(state, cell, ModelPropertiesRef, cell, state.currentModel);
}, state => { }],
[StructureBuilderTags.ModelUnitcell, (state, cell) => {
if (!state.currentModel) return false;
state.currentModel.unitcell = createOrUpdateRef(state, cell, ModelUnitcellRef, cell, state.currentModel);
}, state => { }],
[StructureBuilderTags.ModelGenericRepresentation, (state, cell) => {
if (!state.currentModel) return false;
if (!state.currentModel.genericRepresentations) state.currentModel.genericRepresentations = []
createOrUpdateRefList(state, cell, state.currentModel.genericRepresentations, GenericRepresentationRef, cell, state.currentModel);
}, state => { }],
[StructureBuilderTags.Structure, (state, cell) => {
if (state.currentModel) {
state.currentStructure = createOrUpdateRefList(state, cell, state.currentModel.structures, StructureRef, cell, state.currentModel);
@@ -216,7 +234,7 @@ const tagMap: [string, (state: BuildState, cell: StateObjectCell) => boolean | v
if (!state.currentStructure) return false;
state.currentComponent = createOrUpdateRefList(state, cell, state.currentStructure.components, StructureComponentRef, cell, state.currentStructure);
}, state => state.currentComponent = void 0],
[RepresentationProviderTags.Representation, (state, cell) => {
[StructureRepresentationBuilderTags.Representation, (state, cell) => {
if (!state.currentComponent) return false;
createOrUpdateRefList(state, cell, state.currentComponent.representations, StructureRepresentationRef, cell, state.currentComponent);
}, state => { }],
@@ -244,7 +262,7 @@ function isValidCell(cell?: StateObjectCell): cell is StateObjectCell {
function isRemoved(this: BuildState, ref: HierarchyRef) {
const { cell } = ref;
if (isValidCell(cell)) return;
this.removed.push(ref);
this.changed = true;
}
type VisitorCtx = { tree: StateTree, state: BuildState };
@@ -281,7 +299,7 @@ function _doPreOrder(ctx: VisitorCtx, root: StateTransform) {
const genericTarget = state.currentComponent || state.currentModel || state.currentStructure;
if (genericTarget) {
if (!genericTarget.genericRepresentations) genericTarget.genericRepresentations = [];
genericTarget.genericRepresentations.push(createOrUpdateRef(state, cell, GenericRepresentationRef, cell, genericTarget));
createOrUpdateRefList(state, cell, genericTarget.genericRepresentations, GenericRepresentationRef, cell, genericTarget);
}
} else if (state.currentStructure && VolumeStreaming.is(cell.obj)) {
state.currentStructure.volumeStreaming = createOrUpdateRef(state, cell, StructureVolumeStreamingRef, cell, state.currentStructure);

View File

@@ -9,6 +9,7 @@ import { StructureHierarchy, buildStructureHierarchy, ModelRef, StructureCompone
import { PluginComponent } from '../../component';
import { SetUtils } from '../../../mol-util/set';
import { StateTransform } from '../../../mol-state';
import { applyTrajectoryHierarchyPreset } from '../../builder/structure/hierarchy-preset';
interface StructureHierarchyManagerState {
hierarchy: StructureHierarchy,
@@ -41,7 +42,7 @@ export class StructureHierarchyManager extends PluginComponent<StructureHierarch
return this._currentComponentGroups;
}
private _currentSelectionSet: Set<string> | undefined = void 0;
private _currentSelectionSet: Set<StateTransform.Ref> | undefined = void 0;
get seletionSet() {
if (this._currentSelectionSet) return this._currentSelectionSet;
this._currentSelectionSet = new Set();
@@ -59,25 +60,13 @@ export class StructureHierarchyManager extends PluginComponent<StructureHierarch
return this.state.selection;
}
private nextSelection: Set<StateTransform.Ref> = new Set();
private syncCurrent<T extends HierarchyRef>(hierarchy: StructureHierarchy, current: ReadonlyArray<T>, all: ReadonlyArray<T>): T[] {
if (this.nextSelection.size > 0) {
const newCurrent: T[] = [];
for (const r of all) {
if (this.nextSelection.has(r.cell.transform.ref)) {
newCurrent.push(r);
}
}
if (newCurrent.length === 0) return all.length > 0 ? [all[0]] : [];
return newCurrent;
}
if (current.length === 0) return all.length > 0 ? [all[0]] : [];
private syncCurrent<T extends HierarchyRef>(all: ReadonlyArray<T>, added: Set<StateTransform.Ref>): T[] {
const current = this.seletionSet;
const newCurrent: T[] = [];
for (const c of current) {
const ref = hierarchy.refs.get(c.cell.transform.ref) as T;
if (ref) newCurrent.push(ref);
for (const r of all) {
const ref = r.cell.transform.ref;
if (current.has(ref) || added.has(ref)) newCurrent.push(r);
}
if (newCurrent.length === 0) return all.length > 0 ? [all[0]] : [];
@@ -86,20 +75,18 @@ export class StructureHierarchyManager extends PluginComponent<StructureHierarch
private sync() {
const update = buildStructureHierarchy(this.plugin.state.data, this.state.hierarchy);
if (update.added.length === 0 && update.updated.length === 0 && update.removed.length === 0) {
if (!update.changed) {
return;
}
const { hierarchy } = update;
const trajectories = this.syncCurrent(hierarchy.trajectories, update.added);
const models = this.syncCurrent(hierarchy.models, update.added);
const structures = this.syncCurrent(hierarchy.structures, update.added);
this._currentComponentGroups = void 0;
this._currentSelectionSet = void 0;
const { hierarchy } = update;
const trajectories = this.syncCurrent(hierarchy, this.state.selection.trajectories, hierarchy.trajectories);
const models = this.syncCurrent(hierarchy, this.state.selection.models, hierarchy.models);
const structures = this.syncCurrent(hierarchy, this.state.selection.structures, hierarchy.structures);
this.nextSelection.clear();
this.updateState({ hierarchy, selection: { trajectories, models, structures } });
this.behaviors.selection.next({ hierarchy, trajectories, models, structures });
}
@@ -127,10 +114,6 @@ export class StructureHierarchyManager extends PluginComponent<StructureHierarch
this._currentComponentGroups = void 0;
this._currentSelectionSet = void 0;
// if (trajectories.length === 0 && hierarchy.trajectories.length > 0) trajectories.push(hierarchy.trajectories[0]);
// if (models.length === 0 && hierarchy.models.length > 0) models.push(hierarchy.models[0]);
// if (structures.length === 0 && hierarchy.structures.length > 0) structures.push(hierarchy.structures[0]);
this.updateState({ selection: { trajectories, models, structures } });
this.behaviors.selection.next({ hierarchy, trajectories, models, structures });
}
@@ -144,10 +127,7 @@ export class StructureHierarchyManager extends PluginComponent<StructureHierarch
createModels(trajectories: ReadonlyArray<TrajectoryRef>, kind: 'single' | 'all' = 'single') {
return this.plugin.dataTransaction(async () => {
this.nextSelection.clear();
for (const trajectory of trajectories) {
this.nextSelection.add(trajectory.cell.transform.ref);
if (trajectory.models.length > 0) {
await this.clearTrajectory(trajectory);
}
@@ -156,19 +136,9 @@ export class StructureHierarchyManager extends PluginComponent<StructureHierarch
const tr = trajectory.cell.obj?.data!;
if (kind === 'all' && tr.length > 1) {
for (let i = 0; i < tr.length; i++) {
const model = await this.plugin.builders.structure.createModel(trajectory.cell, { modelIndex: i }, { isCollapsed: true });
const structure = await this.plugin.builders.structure.createStructure(model, { name: 'deposited', params: {} });
this.nextSelection.add(model.ref);
this.nextSelection.add(structure.ref);
await this.plugin.builders.structure.representation.applyPreset(structure, 'auto', { globalThemeName: 'model-index' });
}
await applyTrajectoryHierarchyPreset(this.plugin, trajectory.cell, 'all-models');
} else {
const model = await this.plugin.builders.structure.createModel(trajectory.cell, { modelIndex: 0 }, { isCollapsed: true });
const structure = await this.plugin.builders.structure.createStructure(model);
this.nextSelection.add(model.ref);
this.nextSelection.add(structure.ref);
await this.plugin.builders.structure.representation.applyPreset(structure, 'auto');
await applyTrajectoryHierarchyPreset(this.plugin, trajectory.cell, 'first-model');
}
}
});
@@ -224,4 +194,29 @@ export namespace StructureHierarchyManager {
return groups;
}
export function getSelectedStructuresDescription(plugin: PluginContext) {
const { structures } = plugin.managers.structure.hierarchy.state.selection;
if (structures.length === 0) return '';
if (structures.length === 1) {
const s = structures[0];
const entryId = s.cell.obj?.data.models[0].entryId;
if (s.model?.trajectory?.models && s.model.trajectory.models.length === 1) return entryId;
if (s.model) return `${s.model.cell.obj?.label} | ${entryId}`;
return entryId;
}
const p = structures[0];
const t = p?.model?.trajectory;
let sameTraj = true;
for (const s of structures) {
if (s?.model?.trajectory !== t) {
sameTraj = false;
break;
}
}
return sameTraj && t ? `${t.cell.obj?.label} | ${structures.length} structures` : `${structures.length} structures`;
}
}

View File

@@ -60,7 +60,7 @@ class StructureMeasurementManager extends PluginComponent<StructureMeasurementMa
const update = this.plugin.state.data.build();
for (const cell of this.state.distances) {
update.to(cell).update((old: any) => {
update.to(cell).update((old: any) => {
old.unitLabel = options.distanceUnitLabel;
old.textColor = options.textColor;
});
@@ -74,7 +74,7 @@ class StructureMeasurementManager extends PluginComponent<StructureMeasurementMa
for (const cell of this.state.dihedrals) {
update.to(cell).update((old: any) => { old.textColor = options.textColor; });
}
if (update.editInfo.count === 0) return;
await PluginCommands.State.Update(this.plugin, { state: this.plugin.state.data, tree: update, options: { doNotLogTiming: true } });

View File

@@ -699,6 +699,7 @@ const CustomModelProperties = PluginStateTransform.BuiltIn({
},
update({ a, b, oldParams, newParams }, ctx: PluginContext) {
return Task.create('Custom Props', async taskCtx => {
b.data = a.data;
b.label = a.label;
b.description = a.description;
for (const name of oldParams.autoAttach) {
@@ -736,17 +737,18 @@ const CustomStructureProperties = PluginStateTransform.BuiltIn({
from: SO.Molecule.Structure,
to: SO.Molecule.Structure,
params: (a, ctx: PluginContext) => {
return ctx.customStructureProperties.getParams(a?.data)
return ctx.customStructureProperties.getParams(a?.data.root)
}
})({
apply({ a, params }, ctx: PluginContext) {
return Task.create('Custom Props', async taskCtx => {
await attachStructureProps(a.data, ctx, taskCtx, params);
await attachStructureProps(a.data.root, ctx, taskCtx, params);
return new SO.Molecule.Structure(a.data, { label: a.label, description: a.description });
});
},
update({ a, b, oldParams, newParams }, ctx: PluginContext) {
return Task.create('Custom Props', async taskCtx => {
b.data = a.data;
b.label = a.label;
b.description = a.description;
for (const name of oldParams.autoAttach) {
@@ -754,7 +756,7 @@ const CustomStructureProperties = PluginStateTransform.BuiltIn({
if (!property) continue;
a.data.customPropertyDescriptors.reference(property.descriptor, false);
}
await attachStructureProps(a.data, ctx, taskCtx, newParams);
await attachStructureProps(a.data.root, ctx, taskCtx, newParams);
return StateTransformer.UpdateResult.Updated;
});
}

View File

@@ -26,7 +26,7 @@ import { Overpaint } from '../../mol-theme/overpaint';
import { Transparency } from '../../mol-theme/transparency';
import { BaseGeometry } from '../../mol-geo/geometry/base';
import { Script } from '../../mol-script/script';
import { getUnitcellRepresentation, UnitcellParams } from '../helpers/model-unitcell';
import { UnitcellParams, UnitcellRepresentation, getUnitcellData } from '../../mol-repr/shape/model/unitcell';
import { DistanceParams, DistanceRepresentation } from '../../mol-repr/shape/loci/distance';
import { getDistanceDataFromStructureSelections, getLabelDataFromStructureSelections, getOrientationDataFromStructureSelections, getAngleDataFromStructureSelections, getDihedralDataFromStructureSelections } from './helpers';
import { LabelParams, LabelRepresentation } from '../../mol-repr/shape/loci/label';
@@ -579,17 +579,24 @@ const ModelUnitcell3D = PluginStateTransform.BuiltIn({
canAutoUpdate({ oldParams, newParams }) {
return true;
},
apply({ a, params }) {
apply({ a, params }, plugin: PluginContext) {
return Task.create('Model Unitcell', async ctx => {
const symmetry = ModelSymmetry.Provider.get(a.data)
if (!symmetry) return StateObject.Null
const repr = await getUnitcellRepresentation(ctx, a.data, params);
const data = getUnitcellData(a.data, symmetry)
const repr = UnitcellRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => UnitcellParams)
await repr.createOrUpdate(params, data).runInContext(ctx);
return new SO.Shape.Representation3D({ repr, source: a }, { label: `Unitcell`, description: symmetry.spacegroup.name });
});
},
update({ a, b, newParams }) {
return Task.create('Model Unitcell', async ctx => {
await getUnitcellRepresentation(ctx, a.data, newParams, b.data.repr);
const symmetry = ModelSymmetry.Provider.get(a.data)
if (!symmetry) return StateTransformer.UpdateResult.Null
const props = { ...b.data.repr.props, ...newParams }
const data = getUnitcellData(a.data, symmetry)
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
b.data.source = a
return StateTransformer.UpdateResult.Updated;
});
}

View File

@@ -70,7 +70,7 @@ export type _State<C extends React.Component> = C extends React.Component<any, i
//
export type CollapsableProps = { initiallyCollapsed?: boolean, header?: string }
export type CollapsableState = { isCollapsed: boolean, header: string, isHidden?: boolean }
export type CollapsableState = { isCollapsed: boolean, header: string, description?: string, isHidden?: boolean }
export abstract class CollapsableControls<P = {}, S = {}, SS = {}> extends PluginUIComponent<P & CollapsableProps, S & CollapsableState, SS> {
toggleCollapsed = () => {
@@ -89,9 +89,10 @@ export abstract class CollapsableControls<P = {}, S = {}, SS = {}> extends Plugi
return <div className={wrapClass}>
<div className='msp-transform-header'>
<button className='msp-btn msp-btn-block msp-btn-collapse' onClick={this.toggleCollapsed}>
<button className='msp-btn msp-btn-block msp-btn-collapse msp-no-overflow' onClick={this.toggleCollapsed}>
<Icon name={this.state.isCollapsed ? 'expand' : 'collapse'} />
{this.state.header}
<small style={{ margin: '0 6px' }}>{this.state.isCollapsed ? '' : this.state.description}</small>
</button>
</div>
{!this.state.isCollapsed && this.renderControls()}

View File

@@ -77,12 +77,12 @@ export namespace ActionMenu {
} else {
cat = items as any;
}
cat!.push({ kind: 'item', label: l, value: v, icon: icon ? icon(x) : void 0, selected: selected ? selected(x) : void 0 });
}
return items as ActionMenu.Items;
}
type Opt = ParamDefinition.Select<any>['options'][0];
const _selectOptions = { value: (o: Opt) => o[0], label: (o: Opt) => o[1], category: (o: Opt) => o[2] };
@@ -127,7 +127,7 @@ class Section extends React.PureComponent<SectionProps, SectionState> {
const header = isItems(props.items) && isHeader(props.items[0]) ? props.items[0] : void 0;
const hasCurrent = header?.isIndependent
? false
? false
: props.multiselect
? ActionMenu.hasSelectedItem(props.items)
: (!!props.current && !!ActionMenu.findItem(props.items, props.current.value)) || ActionMenu.hasSelectedItem(props.items);
@@ -214,9 +214,9 @@ class Section extends React.PureComponent<SectionProps, SectionState> {
}
const Action: React.FC<{
item: ActionMenu.Item,
onSelect: ActionMenu.OnSelect | ActionMenu.OnSelectMany,
multiselect: boolean | undefined,
item: ActionMenu.Item,
onSelect: ActionMenu.OnSelect | ActionMenu.OnSelectMany,
multiselect: boolean | undefined,
current: ActionMenu.Item | undefined }> = ({ item, onSelect, current, multiselect }) => {
const isCurrent = current === item;
return <button className='msp-btn msp-btn-block msp-form-control msp-action-menu-button msp-no-overflow' onClick={() => onSelect(multiselect ? [item] : item as any)} disabled={item.disabled}>

View File

@@ -790,7 +790,7 @@ export class GroupControl extends React.PureComponent<ParamProps<PD.Group<any>>
<div className='msp-control-offset'>
<ParameterControls params={filtered} onEnter={this.props.onEnter} values={this.props.value} onChange={this.onChangeParam} isDisabled={this.props.isDisabled} />
</div>
</div>;
</div>;
}
render() {

View File

@@ -229,7 +229,7 @@ export class Log extends PluginUIComponent<{}, { entries: List<LogEntry> }> {
// const actions = cell.status === 'ok' && <StateObjectActionSelect state={current.state} nodeRef={ref} plugin={this.plugin} />
// if (cell.status === 'error') {
// return <>
// return <>
// <SectionHeader icon='flow-cascade' title={`${cell.obj?.label || transform.transformer.definition.display.name}`} desc={transform.transformer.definition.display.name} />
// <UpdateTransformControl state={current.state} transform={transform} customHeader='none' />
// {actions}
@@ -249,7 +249,7 @@ export class Log extends PluginUIComponent<{}, { entries: List<LogEntry> }> {
// </ExpandGroup>);
// }
// return <>
// return <>
// <SectionHeader icon='flow-cascade' title={`${parent.obj?.label || parent.transform.transformer.definition.display.name}`} desc={parent.transform.transformer.definition.display.name} />
// <UpdateTransformControl state={current.state} transform={parent.transform} customHeader='none' />
// {decorators && <div className='msp-controls-section'>{decorators}</div>}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2020 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>
@@ -7,7 +7,6 @@
import * as React from 'react'
import { PluginUIComponent } from '../base';
import { InteractivityManager } from '../../mol-plugin-state/manager/interactivity';
import { MarkerAction } from '../../mol-util/marker-action';
import { ButtonsType, ModifiersKeys, getButtons, getModifiers, getButton } from '../../mol-util/input/input-observer';
import { SequenceWrapper } from './wrapper';
@@ -16,6 +15,7 @@ import { Subject } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { Color } from '../../mol-util/color';
import { OrderedSet } from '../../mol-data/int';
import { Representation } from '../../mol-repr/representation';
type SequenceProps = {
sequenceWrapper: SequenceWrapper.Any,
@@ -32,12 +32,12 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
private lastMouseOverSeqIdx = -1;
private highlightQueue = new Subject<{ seqIdx: number, buttons: number, button: number, modifiers: ModifiersKeys }>();
private lociHighlightProvider = (loci: InteractivityManager.Loci, action: MarkerAction) => {
private lociHighlightProvider = (loci: Representation.Loci, action: MarkerAction) => {
const changed = this.props.sequenceWrapper.markResidue(loci.loci, action)
if (changed) this.updateMarker();
}
private lociSelectionProvider = (loci: InteractivityManager.Loci, action: MarkerAction) => {
private lociSelectionProvider = (loci: Representation.Loci, action: MarkerAction) => {
const changed = this.props.sequenceWrapper.markResidue(loci.loci, action)
if (changed) this.updateMarker();
}
@@ -88,7 +88,7 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
}
hover(loci: StructureElement.Loci | undefined, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys) {
const ev = { current: InteractivityManager.Loci.Empty, buttons, button, modifiers }
const ev = { current: Representation.Loci.Empty, buttons, button, modifiers }
if (loci !== undefined && !StructureElement.Loci.isEmpty(loci)) {
ev.current = { loci };
}
@@ -96,7 +96,7 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
}
click(loci: StructureElement.Loci | undefined, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys) {
const ev = { current: InteractivityManager.Loci.Empty, buttons, button, modifiers }
const ev = { current: Representation.Loci.Empty, buttons, button, modifiers }
if (loci !== undefined && !StructureElement.Loci.isEmpty(loci)) {
ev.current = { loci };
}

View File

@@ -195,7 +195,8 @@
stroke: $font-color;
}
.msp-no-overflow {
.msp-no-overflow {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

View File

@@ -38,6 +38,7 @@
height: $row-height;
margin-top: 1px;
width: 100%;
background: $default-background;
> button {
margin: 0;
@@ -49,6 +50,10 @@
> button:last-child {
margin-right: 0;
}
> button.msp-control-button-label {
background: $default-background;
}
}
.msp-select-row {
@@ -327,9 +332,9 @@
text-align: left;
}
.msp-action-menu-button {
margin-top: 1px;
margin-top: 1px;
.msp-icon {
font-size: 80%;
margin-right: 6px;

View File

@@ -14,6 +14,7 @@ import { ActionMenu } from '../controls/action-menu';
import { ApplyActionControl } from './apply-action';
import { ControlGroup } from '../controls/common';
import { UpdateTransformControl } from './update-transform';
import { StateTreeSpine } from '../../mol-state/tree/spine';
export class StateTree extends PluginUIComponent<{ state: State }, { showActions: boolean }> {
state = { showActions: true };
@@ -254,6 +255,19 @@ class StateTreeNodeLabel extends PluginUIComponent<{ cell: StateObjectCell, dept
(item?.value as any)();
}
updates() {
const cell = this.props.cell;
const decoratorChain = StateTreeSpine.getDecoratorChain(cell.parent, cell.transform.ref);
const decorators = [];
for (let i = decoratorChain.length - 1; i >= 0; i--) {
const d = decoratorChain[i];
decorators!.push(<UpdateTransformControl key={`${d.transform.transformer.id}-${i}`} state={cell.parent} transform={d.transform} noMargin wrapInExpander />);
}
return decorators;
}
render() {
const cell = this.props.cell;
const n = cell.transform;
@@ -315,38 +329,11 @@ class StateTreeNodeLabel extends PluginUIComponent<{ cell: StateObjectCell, dept
let actions = this.actions;
return <div style={{ marginBottom: '1px' }}>
{row}
<UpdateTransformControl state={cell.parent} transform={cell.transform} noMargin wrapInExpander />
{this.updates()}
{actions && <ActionMenu items={actions} onSelect={this.selectAction} />}
</div>
}
// if (this.state.isCurrent) {
// return <>
// {row}
// <StateTreeNodeTransform {...this.props} toggleCollapsed={this.toggleUpdaterObs} />
// </>
// }
return row;
}
}
// class StateTreeNodeTransform extends PluginUIComponent<{ nodeRef: string, state: State, depth: number, toggleCollapsed?: Observable<any> }> {
// componentDidMount() {
// // this.subscribe(this.plugin.events.state.object.updated, ({ ref, state }) => {
// // if (this.props.nodeRef !== ref || this.props.state !== state) return;
// // this.forceUpdate();
// // });
// }
// render() {
// const ref = this.props.nodeRef;
// const cell = this.props.state.cells.get(ref)!;
// const parent: StateObjectCell | undefined = (cell.sourceRef && this.props.state.cells.get(cell.sourceRef)!) || void 0;
// if (!parent || parent.status !== 'ok') return null;
// const transform = cell.transform;
// return <UpdateTransformContol state={this.props.state} transform={transform} initiallyCollapsed={true} toggleCollapsed={this.props.toggleCollapsed} />;
// }
// }
}

View File

@@ -18,6 +18,7 @@ import { ParameterControls } from '../controls/parameters';
import { UpdateTransformControl } from '../state/update-transform';
import { PluginContext } from '../../mol-plugin/context';
import { getStructureThemeTypes } from '../../mol-plugin-state/helpers/structure-representation-params';
import { StructureHierarchyManager } from '../../mol-plugin-state/manager/structure/hierarchy';
interface StructureComponentControlState extends CollapsableState {
isDisabled: boolean
@@ -28,6 +29,12 @@ export class StructureComponentControls extends CollapsableControls<{}, Structur
return { header: 'Representation', isCollapsed: false, isDisabled: false };
}
componentDidMount() {
this.subscribe(this.plugin.managers.structure.hierarchy.behaviors.selection, c => this.setState({
description: StructureHierarchyManager.getSelectedStructuresDescription(this.plugin)
}));
}
renderControls() {
return <>
<ComponentEditorControls />
@@ -81,11 +88,9 @@ class ComponentEditorControls extends PurePluginUIComponent<{}, ComponentEditorC
}
get presetActions() {
const actions = [
ActionMenu.Item('Clear', null),
];
const actions = [];
const pivot = this.plugin.managers.structure.component.pivotStructure;
const providers = this.plugin.builders.structure.representation.getPresets(pivot?.cell.obj?.data)
const providers = this.plugin.builders.structure.representation.getPresets(pivot?.cell.obj)
for (const p of providers) {
actions.push(ActionMenu.Item(p.display.name, p));
}
@@ -246,9 +251,9 @@ class StructureComponentGroup extends PurePluginUIComponent<{ group: StructureCo
const repr = this.pivot.representations[0];
const name = repr.cell.transform.params?.colorTheme.name;
const themes = getStructureThemeTypes(this.plugin, this.pivot.cell.obj?.data);
return ActionMenu.createItemsFromSelectOptions(themes, {
return ActionMenu.createItemsFromSelectOptions(themes, {
value: o => () => mng.updateRepresentationsTheme(this.props.group, { color: o[0] }),
selected: o => o[0] === name
selected: o => o[0] === name
}) as ActionMenu.Item[];
}
@@ -282,7 +287,7 @@ class StructureComponentGroup extends PurePluginUIComponent<{ group: StructureCo
return ret;
}
selectAction: ActionMenu.OnSelect = item => {
if (!item) return;
this.setState({ action: void 0 });
@@ -333,15 +338,13 @@ class StructureComponentGroup extends PurePluginUIComponent<{ group: StructureCo
const cell = component.cell;
const label = cell.obj?.label;
return <>
<div className='msp-control-row'>
<button className='msp-control-button-label' title={`${label}. Click to focus.`} onClick={this.focus} onMouseEnter={this.highlight} onMouseLeave={this.clearHighlight} style={{ textAlign: 'left' }}>
<div className='msp-btn-row-group'>
<button className='msp-form-control msp-control-button-label' title={`${label}. Click to focus.`} onClick={this.focus} onMouseEnter={this.highlight} onMouseLeave={this.clearHighlight} style={{ textAlign: 'left' }}>
{label}
</button>
<div className='msp-select-row'>
<IconButton onClick={this.toggleVisible} icon='visual-visibility' toggleState={!cell.state.isHidden} title={`${cell.state.isHidden ? 'Show' : 'Hide'} component`} small />
<IconButton onClick={this.toggleRemove} icon='remove' title='Remove' small toggleState={this.state.action === 'remove'} />
<IconButton onClick={this.toggleAction} icon='dot-3' title='Actions' toggleState={this.state.action === 'action'} />
</div>
<IconButton onClick={this.toggleVisible} icon='visual-visibility' toggleState={!cell.state.isHidden} title={`${cell.state.isHidden ? 'Show' : 'Hide'} component`} small customClass='msp-form-control' style={{ flex: '0 0 32px' }} />
<IconButton onClick={this.toggleRemove} icon='remove' title='Remove' small toggleState={this.state.action === 'remove'} customClass='msp-form-control' style={{ flex: '0 0 32px' }} />
<IconButton onClick={this.toggleAction} icon='dot-3' title='Actions' toggleState={this.state.action === 'action'} customClass='msp-form-control' style={{ flex: '0 0 32px', padding: '0px' }} />
</div>
{this.state.action === 'remove' && <ActionMenu items={this.removeActions} onSelect={this.selectRemoveAction} />}
{this.state.action === 'action' && <>

View File

@@ -276,7 +276,7 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
this.props.cell.sourceRef
return [ActionMenu.Item('Select This', 'flash', () => this.plugin.managers.structure.selection.fromSelections(this.props.cell.sourceRef!))];
}
selectAction: ActionMenu.OnSelect = item => {
if (!item) return;
this.setState({ showUpdate: false });
@@ -290,18 +290,18 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
return <>
<div className='msp-btn-row-group' key={obj.id} onMouseEnter={this.highlight} onMouseLeave={this.clearHighlight}>
<button className='msp-btn msp-btn-block msp-form-control msp-no-overflow' title='Click to focus. Hover to highlight.' onClick={this.focus} style={{ width: 'auto', textAlign: 'left' }}>
<button className='msp-form-control msp-control-button-label msp-no-overflow' title='Click to focus. Hover to highlight.' onClick={this.focus} style={{ width: 'auto', textAlign: 'left' }}>
<span dangerouslySetInnerHTML={{ __html: this.label }} />
</button>
<IconButton small={true} customClass='msp-form-control' onClick={this.toggleVisibility} icon='eye' style={{ flex: '0 0 32px' }} title={cell.state.isHidden ? 'Show' : 'Hide'} toggleState={!cell.state.isHidden} />
<IconButton small={true} customClass='msp-form-control' onClick={this.delete} icon='remove' style={{ flex: '0 0 32px' }} title='Delete' />
<IconButton small={true} customClass='msp-form-control' onClick={this.toggleUpdate} icon='dot-3' style={{ flex: '0 0 32px' }} title={cell.state.isHidden ? 'Show' : 'Hide'} toggleState={this.state.showUpdate} />
<IconButton small customClass='msp-form-control' onClick={this.toggleVisibility} icon='eye' style={{ flex: '0 0 32px' }} title={cell.state.isHidden ? 'Show' : 'Hide'} toggleState={!cell.state.isHidden} />
<IconButton small customClass='msp-form-control' onClick={this.delete} icon='remove' style={{ flex: '0 0 32px' }} title='Delete' />
<IconButton customClass='msp-form-control' onClick={this.toggleUpdate} icon='dot-3' style={{ flex: '0 0 32px', padding: '0px' }} title='Actions' toggleState={this.state.showUpdate} />
</div>
{this.state.showUpdate && <>
<ActionMenu items={this.actions} onSelect={this.selectAction} />
<div className='msp-control-offset'>
<ExpandGroup header='Options' noOffset>
<UpdateTransformControl state={cell.parent} transform={cell.transform} customHeader='none' />
<UpdateTransformControl state={cell.parent} transform={cell.transform} customHeader='none' autoHideApply />
</ExpandGroup>
</div>
</>}

View File

@@ -14,6 +14,7 @@ import { PluginCommands } from '../../mol-plugin/commands';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { memoize1 } from '../../mol-util/memoize';
import { StructureHierarchyManager } from '../../mol-plugin-state/manager/structure/hierarchy';
import { UnitcellEntry } from './unitcell';
interface StructureSourceControlState extends CollapsableState {
isBusy: boolean,
@@ -191,13 +192,13 @@ export class StructureSourceControls extends CollapsableControls<{}, StructureSo
const ret: ActionMenu.Items = [];
const { selection } = this.plugin.managers.structure.hierarchy;
if (selection.trajectories.some(t => t.cell.obj?.data.length! > 1)) {
if (selection.trajectories.some(t => t.cell.obj?.data.length! > 1 && t.cell.obj?.data.length! !== t.models.length)) {
ret.push(ActionMenu.Item('Load all models', () => this.plugin.managers.structure.hierarchy.createModels(selection.trajectories, 'all')));
}
if (selection.trajectories.some(t => t.models.length > 1)) {
ret.push(ActionMenu.Item('Load single model', () => this.plugin.managers.structure.hierarchy.createModels(selection.trajectories, 'single')));
}
// TODO: remove actions?
return ret;
}
@@ -245,6 +246,24 @@ export class StructureSourceControls extends CollapsableControls<{}, StructureSo
return <ParameterControls params={params} values={s.cell.params?.values} onChangeValues={this.updateStructure} isDisabled={this.state.isBusy} />
}
get unitcell() {
const { selection } = this.plugin.managers.structure.hierarchy;
if (selection.structures.length !== 1) return null;
const model = selection.structures[0].model
if (!model) return null
const unitcell = model.unitcell
if (!unitcell) {
// this.plugin.builders.structure.createUnitcell(model.cell, undefined, { isHidden: true })
return null
} else if (!unitcell.cell.obj) {
return null;
}
return <UnitcellEntry key={unitcell.cell.obj.id} cell={unitcell.cell} />
}
renderControls() {
const disabled = this.state.isBusy || this.isEmpty;
const actions = this.actions(this.plugin.managers.structure.hierarchy.selection);
@@ -260,6 +279,7 @@ export class StructureSourceControls extends CollapsableControls<{}, StructureSo
{this.state.show === 'actions' && <ActionMenu items={actions} onSelect={this.selectAction} />}
{this.modelIndex}
{this.structureType}
{this.unitcell}
</>;
}
}

View File

@@ -0,0 +1,74 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react';
import { PurePluginUIComponent } from '../base';
import { StateTransformer, StateTransform, StateObjectCell } from '../../mol-state';
import { IconButton } from '../controls/common';
import { UpdateTransformControl } from '../state/update-transform';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { PluginCommands } from '../../mol-plugin/commands';
export type UnitcellCell = StateObjectCell<PluginStateObject.Shape.Representation3D, StateTransform<StateTransformer<PluginStateObject.Molecule.Model, PluginStateObject.Shape.Representation3D, any>>>
export class UnitcellEntry extends PurePluginUIComponent<{ cell: UnitcellCell }, { showOptions: boolean }> {
state = { showOptions: false }
componentDidMount() {
this.subscribe(this.plugin.events.state.cell.stateUpdated, e => {
this.forceUpdate();
});
}
toggleVisibility = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
PluginCommands.State.ToggleVisibility(this.plugin, { state: this.props.cell.parent, ref: this.props.cell.transform.ref });
e.currentTarget.blur();
}
highlight = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
PluginCommands.Interactivity.Object.Highlight(this.plugin, { state: this.props.cell.parent, ref: this.props.cell.transform.ref });
}
clearHighlight = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
PluginCommands.Interactivity.ClearHighlights(this.plugin);
}
focus = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
if (!this.props.cell.state.isHidden) {
const loci = this.props.cell.obj?.data.repr.getLoci()
if (loci) this.plugin.managers.camera.focusLoci(loci);
}
}
toggleOptions = () => this.setState({ showOptions: !this.state.showOptions })
render() {
const { cell } = this.props;
const { obj } = cell;
if (!obj) return null;
const { label, description } = obj
return <>
<div className='msp-btn-row-group' style={{ marginTop: '6px' }}>
<button className='msp-form-control msp-control-button-label' title={`${label}. Click to focus.`} onClick={this.focus} onMouseEnter={this.highlight} onMouseLeave={this.clearHighlight} style={{ textAlign: 'left' }}>
{label} <small>{description}</small>
</button>
<IconButton customClass='msp-form-control' onClick={this.toggleVisibility} icon='visual-visibility' toggleState={!cell.state.isHidden} title={`${cell.state.isHidden ? 'Show' : 'Hide'}`} small style={{ flex: '0 0 32px' }} />
<IconButton customClass='msp-form-control' onClick={this.toggleOptions} icon='dot-3' title='Options' toggleState={this.state.showOptions} style={{ flex: '0 0 32px', padding: '0px' }} />
</div>
{this.state.showOptions && <>
<div className='msp-control-offset'>
<UpdateTransformControl state={cell.parent} transform={cell.transform} customHeader='none' autoHideApply />
</div>
</>}
</>;
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -10,7 +10,6 @@ import { PluginContext } from '../../../mol-plugin/context';
import { PluginStateObject as SO } from '../../../mol-plugin-state/objects';
import { lociLabel } from '../../../mol-theme/label';
import { PluginBehavior } from '../behavior';
import { InteractivityManager } from '../../../mol-plugin-state/manager/interactivity';
import { StateTreeSpine } from '../../../mol-state/tree/spine';
import { StateSelection } from '../../../mol-state';
import { ButtonsType, ModifiersKeys } from '../../../mol-util/input/input-observer';
@@ -19,6 +18,7 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { EmptyLoci, Loci } from '../../../mol-model/loci';
import { Structure } from '../../../mol-model/structure';
import { arrayMax } from '../../../mol-util/array';
import { Representation } from '../../../mol-repr/representation';
const B = ButtonsType
const M = ModifiersKeys
@@ -39,7 +39,7 @@ export const HighlightLoci = PluginBehavior.create({
name: 'representation-highlight-loci',
category: 'interaction',
ctor: class extends PluginBehavior.Handler<HighlightLociProps> {
private lociMarkProvider = (interactionLoci: InteractivityManager.Loci, action: MarkerAction) => {
private lociMarkProvider = (interactionLoci: Representation.Loci, action: MarkerAction) => {
if (!this.ctx.canvas3d) return;
this.ctx.canvas3d.mark({ loci: interactionLoci.loci }, action)
}
@@ -92,9 +92,9 @@ export const SelectLoci = PluginBehavior.create({
category: 'interaction',
ctor: class extends PluginBehavior.Handler<SelectLociProps> {
private spine: StateTreeSpine.Impl
private lociMarkProvider = (interactionLoci: InteractivityManager.Loci, action: MarkerAction) => {
private lociMarkProvider = (reprLoci: Representation.Loci, action: MarkerAction) => {
if (!this.ctx.canvas3d) return;
this.ctx.canvas3d.mark({ loci: interactionLoci.loci }, action)
this.ctx.canvas3d.mark({ loci: reprLoci.loci }, action)
}
private applySelectMark(ref: string, clear?: boolean) {
const cell = this.ctx.state.data.cells.get(ref)
@@ -111,10 +111,10 @@ export const SelectLoci = PluginBehavior.create({
}
}
register() {
const lociIsEmpty = (current: InteractivityManager.Loci) => Loci.isEmpty(current.loci)
const lociIsNotEmpty = (current: InteractivityManager.Loci) => !Loci.isEmpty(current.loci)
const lociIsEmpty = (current: Representation.Loci) => Loci.isEmpty(current.loci)
const lociIsNotEmpty = (current: Representation.Loci) => !Loci.isEmpty(current.loci)
const actions: [keyof typeof DefaultSelectLociBindings, (current: InteractivityManager.Loci) => void, ((current: InteractivityManager.Loci) => boolean) | undefined][] = [
const actions: [keyof typeof DefaultSelectLociBindings, (current: Representation.Loci) => void, ((current: Representation.Loci) => boolean) | undefined][] = [
['clickSelect', current => this.ctx.managers.interactivity.lociSelects.select(current), lociIsNotEmpty],
['clickToggle', current => this.ctx.managers.interactivity.lociSelects.toggle(current), lociIsNotEmpty],
['clickToggleExtend', current => this.ctx.managers.interactivity.lociSelects.toggleExtend(current), lociIsNotEmpty],

View File

@@ -55,8 +55,6 @@ const StructureRepresentationInteractionParams = (plugin: PluginContext) => {
type StructureRepresentationInteractionProps = PD.ValuesFor<ReturnType<typeof StructureRepresentationInteractionParams>>
//PD.Values<typeof StructureRepresentationInteractionParams>
export enum StructureRepresentationInteractionTags {
Group = 'structure-interaction-group',
ResidueSel = 'structure-interaction-residue-sel',
@@ -105,13 +103,13 @@ export class StructureRepresentationInteractionBehavior extends PluginBehavior.W
if (!refs[StructureRepresentationInteractionTags.SurrRepr]) {
refs[StructureRepresentationInteractionTags.SurrRepr] = builder
.to(refs['structure-interaction-surr-sel']!)
.apply(StateTransforms.Representation.StructureRepresentation3D,this.params.nciParams, { tags: StructureRepresentationInteractionTags.SurrRepr }).ref;
.apply(StateTransforms.Representation.StructureRepresentation3D,this.params.surroundingsParams, { tags: StructureRepresentationInteractionTags.SurrRepr }).ref;
}
if (!refs[StructureRepresentationInteractionTags.SurrNciRepr]) {
refs[StructureRepresentationInteractionTags.SurrNciRepr] = builder
.to(refs['structure-interaction-surr-sel']!)
.apply(StateTransforms.Representation.StructureRepresentation3D, this.params.surroundingsParams, { tags: StructureRepresentationInteractionTags.SurrNciRepr }).ref;
.apply(StateTransforms.Representation.StructureRepresentation3D, this.params.nciParams, { tags: StructureRepresentationInteractionTags.SurrNciRepr }).ref;
}
return { state, builder, refs };
@@ -213,7 +211,7 @@ export class StructureRepresentationInteractionBehavior extends PluginBehavior.W
const state = this.plugin.state.data;
const builder = state.build();
const all = StateSelection.Generators.root.subtree();
const all = StateSelection.Generators.root.subtree();
for (const repr of state.select(all.withTag(StructureRepresentationInteractionTags.ResidueRepr))) {
builder.to(repr).update(this.params.focusParams);
}

View File

@@ -276,7 +276,9 @@ export namespace VolumeStreaming {
const parent = this.plugin.helpers.substructureParent.get(loci.structure);
if (!parent) return Box3D.empty();
const root = this.getStructureRoot();
if (!root || !root.obj || root.obj !== parent.obj) return Box3D.empty();
if (!root || !root.obj) return Box3D.empty();
const rootParent = this.plugin.helpers.substructureParent.get(root.obj.data);
if (parent !== rootParent) return Box3D.empty();
const extendedLoci = StructureElement.Loci.extendToWholeResidues(loci)
const box = StructureElement.Loci.getBoundary(extendedLoci).box

View File

@@ -22,6 +22,7 @@ import { Theme } from '../../../../mol-theme/theme';
import { Box3D } from '../../../../mol-math/geometry';
import { Vec3 } from '../../../../mol-math/linear-algebra';
import { PluginConfig } from '../../../config';
import { Model } from '../../../../mol-model/structure';
function addEntry(entries: InfoEntryProps[], method: VolumeServerInfo.Kind, dataId: string, emDefaultContourLevel: number) {
entries.push({
@@ -54,7 +55,7 @@ export const InitVolumeStreaming = StateAction.build({
isApplicable: (a, _, plugin: PluginContext) => {
const canStreamTest = plugin.config.get(PluginConfig.VolumeStreaming.CanStream);
if (canStreamTest) return canStreamTest(a.data, plugin);
return a.data.models.length === 1;
return a.data.models.length === 1 && Model.hasDensityMap(a.data.models[0]);
}
})(({ ref, state, params }, plugin: PluginContext) => Task.create('Volume Streaming', async taskCtx => {
const entries: InfoEntryProps[] = []

View File

@@ -81,6 +81,7 @@ export function UpdateRepresentationVisibility(ctx: PluginContext) {
const cell = e.state.cells.get(e.ref)!;
if (!SO.isRepresentation3D(cell.obj)) return;
updateVisibility(cell, cell.obj.data.repr);
ctx.canvas3d?.syncVisibility();
ctx.canvas3d?.requestDraw(true);
})
}

View File

@@ -2,9 +2,10 @@
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Structure } from '../mol-model/structure';
import { Structure, Model } from '../mol-model/structure';
import { PluginContext } from './context';
export class PluginConfigItem<T = any> {
@@ -23,7 +24,11 @@ export const PluginConfig = {
},
VolumeStreaming: {
DefaultServer: item('volume-streaming.server', 'https://ds.litemol.org'),
CanStream: item('volume-streaming.can-stream', (s: Structure, plugin: PluginContext) => s.models.length === 1)
CanStream: item('volume-streaming.can-stream', (s: Structure, plugin: PluginContext) => {
return s.models.length === 1 && (Model.hasDensityMap(s.models[0])
// the following test is to include e.g. 'updated' files from PDBe
|| (!Model.isFromPdbArchive(s.models[0]) && s.models[0].entryId.length === 4))
})
}
}

View File

@@ -49,6 +49,7 @@ import { TaskManager } from './util/task-manager';
import { PluginToastManager } from './util/toast';
import { ViewportScreenshotHelper } from './util/viewport-screenshot';
import { PLUGIN_VERSION, PLUGIN_VERSION_DATE } from './version';
import { Representation } from '../mol-repr/representation';
export class PluginContext {
private disposed = false;
@@ -90,8 +91,8 @@ export class PluginContext {
isBusy: this.ev.behavior<boolean>(false)
},
interaction: {
hover: this.ev.behavior<InteractivityManager.HoverEvent>({ current: InteractivityManager.Loci.Empty, modifiers: ModifiersKeys.None, buttons: 0, button: 0 }),
click: this.ev.behavior<InteractivityManager.ClickEvent>({ current: InteractivityManager.Loci.Empty, modifiers: ModifiersKeys.None, buttons: 0, button: 0 })
hover: this.ev.behavior<InteractivityManager.HoverEvent>({ current: Representation.Loci.Empty, modifiers: ModifiersKeys.None, buttons: 0, button: 0 }),
click: this.ev.behavior<InteractivityManager.ClickEvent>({ current: Representation.Loci.Empty, modifiers: ModifiersKeys.None, buttons: 0, button: 0 })
},
labels: {
highlight: this.ev.behavior<{ entries: ReadonlyArray<LociLabelEntry> }>({ entries: [] })

View File

@@ -5,39 +5,91 @@
*/
import { Structure } from '../../mol-model/structure';
import { State, StateObject, StateSelection, StateObjectCell } from '../../mol-state';
import { State, StateObject, StateSelection, StateObjectCell, StateTransform } from '../../mol-state';
import { PluginContext } from '../context';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { arraySetAdd, arraySetRemove } from '../../mol-util/array';
export { SubstructureParentHelper };
class SubstructureParentHelper {
private decorators = new Map<string, string[]>();
private root = new Map<Structure, { ref: string, count: number }>();
private tracked = new Map<string, Structure>();
/** Returns the root node of given structure if existing */
get(s: Structure): StateObjectCell<PluginStateObject.Molecule.Structure> | undefined {
/** Returns the root node of given structure if existing, takes decorators into account */
get(s: Structure, ignoreDecorators = false): StateObjectCell<PluginStateObject.Molecule.Structure> | undefined {
const r = this.root.get(s);
if (!r) return;
return this.plugin.state.data.cells.get(r.ref);
const decorators = this.decorators.get(r.ref);
if (ignoreDecorators || !decorators) return this.plugin.state.data.cells.get(r.ref);
return this.plugin.state.data.cells.get(this.findDeepestDecorator(r.ref, decorators));
}
private findDeepestDecorator(ref: string, decorators: string[]) {
if (decorators.length === 0) return ref;
if (decorators.length === 1) return decorators[0];
const cells = this.plugin.state.data.cells;
let depth = 0, ret = ref;
for (const dr of decorators) {
let c = cells.get(dr);
let d = 0;
while (c && c.transform.ref !== StateTransform.RootRef) {
d++;
c = cells.get(c.transform.parent);
}
if (d > depth) {
ret = dr;
depth = d;
}
}
return ret;
}
private addDecorator(root: string, ref: string) {
if (this.decorators.has(root)) {
arraySetAdd(this.decorators.get(root)!, ref);
} else {
this.decorators.set(root, [ref]);
}
}
private tryRemoveDecorator(root: string, ref: string) {
if (this.decorators.has(root)) {
const xs = this.decorators.get(root)!;
arraySetRemove(xs, ref);
if (xs.length === 0) this.decorators.delete(root);
}
}
private addMapping(state: State, ref: string, obj: StateObject) {
if (!PluginStateObject.Molecule.Structure.is(obj)) return;
const parent = state.select(StateSelection.Generators.byRef(ref).rootOfType([PluginStateObject.Molecule.Structure]))[0];
this.tracked.set(ref, obj.data);
let parentRef;
// if the structure is already present in the tree, do not rewrite the root.
if (this.root.has(obj.data)) {
this.root.get(obj.data)!.count++;
return;
const e = this.root.get(obj.data)!;
parentRef = e.ref;
e.count++;
} else {
const parent = state.select(StateSelection.Generators.byRef(ref).rootOfType([PluginStateObject.Molecule.Structure]))[0];
if (!parent) {
this.root.set(obj.data, { ref, count: 1 });
} else {
parentRef = parent.transform.ref;
this.root.set(obj.data, { ref: parentRef, count: 1 });
}
}
if (!parent) {
this.root.set(obj.data, { ref, count : 1 });
} else {
this.root.set(obj.data, { ref: parent.transform.ref, count: 1 });
if (!parentRef) return;
const cell = state.cells.get(ref);
if (cell?.transform.isDecorator) {
this.addDecorator(parentRef, ref);
}
}
@@ -48,6 +100,9 @@ class SubstructureParentHelper {
this.tracked.delete(ref);
const root = this.root.get(s)!;
this.tryRemoveDecorator(root.ref, ref);
if (root.count > 1) {
root.count--;
} else {

View File

@@ -0,0 +1,100 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Model, Symmetry } from '../../../mol-model/structure';
import { ShapeRepresentation } from '../representation';
import { Shape } from '../../../mol-model/shape';
import { ColorNames } from '../../../mol-util/color/names';
import { RuntimeContext } from '../../../mol-task';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
import { BoxCage } from '../../../mol-geo/primitive/box';
import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
import { transformCage, cloneCage } from '../../../mol-geo/primitive/cage';
import { Sphere3D } from '../../../mol-math/geometry';
import { RepresentationParamsGetter, Representation, RepresentationContext } from '../../representation';
const translate05 = Mat4.fromTranslation(Mat4(), Vec3.create(0.5, 0.5, 0.5))
const unitCage = transformCage(cloneCage(BoxCage()), translate05)
const tmpRef = Vec3()
const tmpTranslate = Mat4()
interface UnitcellData {
symmetry: Symmetry
ref: Vec3
}
const CellParams = {
...Mesh.Params,
cellColor: PD.Color(ColorNames.orange),
cellScale: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 })
}
type MeshParams = typeof CellParams
const UnitcellVisuals = {
'mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<UnitcellData, MeshParams>) => ShapeRepresentation(getUnitcellShape, Mesh.Utils),
}
export const UnitcellParams = {
...CellParams
}
export type UnitcellParams = typeof UnitcellParams
export type UnitcellProps = PD.Values<UnitcellParams>
function getUnitcellMesh(data: UnitcellData, props: UnitcellProps, mesh?: Mesh) {
const state = MeshBuilder.createState(256, 128, mesh)
const { fromFractional } = data.symmetry.spacegroup.cell
Vec3.floor(tmpRef, data.ref)
Mat4.fromTranslation(tmpTranslate, tmpRef)
const cellCage = transformCage(cloneCage(unitCage), tmpTranslate)
const radius = (Math.cbrt(data.symmetry.spacegroup.cell.volume) / 300) * props.cellScale
state.currentGroup = 1
MeshBuilder.addCage(state, fromFractional, cellCage, radius, 2, 20)
const cpA = Vec3.create(0, 0, 0)
Vec3.transformMat4(cpA, Vec3.add(cpA, cpA, tmpRef), fromFractional)
const cpB = Vec3.create(1, 1, 1)
Vec3.transformMat4(cpB, Vec3.add(cpB, cpB, tmpRef), fromFractional)
const cpC = Vec3.create(1, 0, 0)
Vec3.transformMat4(cpC, Vec3.add(cpC, cpC, tmpRef), fromFractional)
const cpD = Vec3.create(0, 1, 1)
Vec3.transformMat4(cpD, Vec3.add(cpD, cpD, tmpRef), fromFractional)
const center = Vec3()
Vec3.add(center, cpA, cpB)
Vec3.scale(center, center, 0.5)
const d = Math.max(Vec3.distance(cpA, cpB), Vec3.distance(cpC, cpD))
const sphere = Sphere3D.create(center, d / 2 + radius)
const m = MeshBuilder.getMesh(state)
m.setBoundingSphere(sphere)
return m
}
function getUnitcellShape(ctx: RuntimeContext, data: UnitcellData, props: UnitcellProps, shape?: Shape<Mesh>) {
const geo = getUnitcellMesh(data, props, shape && shape.geometry);
const label = Symmetry.getUnitcellLabel(data.symmetry)
return Shape.create(label, data, geo, () => props.cellColor, () => 1, () => label)
}
//
export function getUnitcellData(model: Model, symmetry: Symmetry) {
return {
symmetry,
ref: Vec3.transformMat4(Vec3(), Model.getCenter(model), symmetry.spacegroup.cell.toFractional)
}
}
export type UnitcellRepresentation = Representation<UnitcellData, UnitcellParams>
export function UnitcellRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<UnitcellData, UnitcellParams>): UnitcellRepresentation {
return Representation.createMulti('Unitcell', ctx, getParams, Representation.StateBuilder, UnitcellVisuals as unknown as Representation.Def<UnitcellData, UnitcellParams>)
}

View File

@@ -90,13 +90,13 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
applyMarkerAction(arr, Interval.ofBounds(0, arr.length), MarkerAction.RemoveHighlight);
}
} else {
// console.log(label, 'not found visualGroup to reuse, creating new')
// console.log(label, 'did not find visualGroup to reuse, creating new')
// newGroups.push(group)
const visual = visualCtor(materialId)
// ensure state is current for new visual
setVisualState(visual, group, _state)
const promise = visual.createOrUpdate({ webgl: ctx.webgl, runtime }, _theme, _props, { group, structure })
if (promise) await promise
// ensure state is current for new visual
setVisualState(visual, group, _state)
visuals.set(group.hashCode, { visual, group })
}
if (runtime.shouldUpdate) await runtime.update({ message: 'Creating or updating UnitsVisual', current: i, max: _groups.length })

View File

@@ -21,6 +21,7 @@ import { Interval, OrderedSet } from '../../../mol-data/int';
import { isHydrogen } from './util/common';
import { BondType } from '../../../mol-model/structure/model/types';
import { ignoreBondType, BondCylinderParams, BondIterator } from './util/bond';
import { Sphere3D } from '../../../mol-math/geometry';
function createIntraUnitBondCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<IntraUnitBondParams>, mesh?: Mesh) {
if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
@@ -92,7 +93,12 @@ function createIntraUnitBondCylinderMesh(ctx: VisualContext, unit: Unit, structu
ignore: (edgeIndex: number) => ignoreHydrogen(edgeIndex) || ignoreBondType(include, exclude, _flags[edgeIndex])
}
return createLinkCylinderMesh(ctx, builderProps, props, mesh)
const m = createLinkCylinderMesh(ctx, builderProps, props, mesh)
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * sizeFactor)
m.setBoundingSphere(sphere)
return m
}
export const IntraUnitBondParams = {

View File

@@ -14,6 +14,7 @@ import { PointsBuilder } from '../../../mol-geo/geometry/points/points-builder';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { ElementIterator, getElementLoci, eachElement } from './util/element';
import { VisualUpdateState } from '../../util';
import { Sphere3D } from '../../../mol-math/geometry';
export const ElementPointParams = {
...UnitsPointsParams,
@@ -39,7 +40,13 @@ export function createElementPoint(ctx: VisualContext, unit: Unit, structure: St
pos(elements[i], p)
builder.add(p[0], p[1], p[2], i)
}
return builder.getPoints()
const pt = builder.getPoints()
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * props.sizeFactor)
pt.setBoundingSphere(sphere)
return pt
}
export function ElementPointVisual(materialId: number): UnitsVisual<ElementPointParams> {

View File

@@ -20,6 +20,7 @@ import { addEllipsoid } from '../../../mol-geo/geometry/mesh/builder/ellipsoid';
import { AtomSiteAnisotrop } from '../../../mol-model-formats/structure/property/anisotropic'
import { equalEps } from '../../../mol-math/linear-algebra/3d/common';
import { addSphere } from '../../../mol-geo/geometry/mesh/builder/sphere';
import { Sphere3D } from '../../../mol-math/geometry';
export const EllipsoidMeshParams = {
...UnitsMeshParams,
@@ -66,7 +67,7 @@ export function createEllipsoidMesh(ctx: VisualContext, unit: Unit, structure: S
if (!atomSiteAnisotrop) return Mesh.createEmpty(mesh)
const v = Vec3()
const m = Mat3()
const mat = Mat3()
const eigvals = Vec3()
const eigvec1 = Vec3()
const eigvec2 = Vec3()
@@ -87,11 +88,11 @@ export function createEllipsoidMesh(ctx: VisualContext, unit: Unit, structure: S
pos(ei, v)
builderState.currentGroup = i
Tensor.toMat3(m, space, U.value(ai))
Mat3.symmtricFromLower(m, m)
Mat3.symmetricEigenvalues(eigvals, m)
Mat3.eigenvector(eigvec1, m, eigvals[1])
Mat3.eigenvector(eigvec2, m, eigvals[2])
Tensor.toMat3(mat, space, U.value(ai))
Mat3.symmtricFromLower(mat, mat)
Mat3.symmetricEigenvalues(eigvals, mat)
Mat3.eigenvector(eigvec1, mat, eigvals[1])
Mat3.eigenvector(eigvec2, mat, eigvals[2])
for (let j = 0; j < 3; ++j) {
// show 50% probability surface, needs sqrt as U matrix is in angstrom-squared
// take abs of eigenvalue to avoid reflection
@@ -106,5 +107,10 @@ export function createEllipsoidMesh(ctx: VisualContext, unit: Unit, structure: S
}
}
return MeshBuilder.getMesh(builderState)
const m = MeshBuilder.getMesh(builderState)
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * sizeFactor)
m.setBoundingSphere(sphere)
return m
}

View File

@@ -49,6 +49,9 @@ async function createGaussianSurfaceMesh(ctx: VisualContext, unit: Unit, structu
Mesh.transform(surface, transform)
if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface)
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.radiusOffset)
surface.setBoundingSphere(sphere)
return surface
}
@@ -93,6 +96,9 @@ async function createStructureGaussianSurfaceMesh(ctx: VisualContext, structure:
Mesh.transform(surface, transform)
if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface)
const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, props.radiusOffset)
surface.setBoundingSphere(sphere)
return surface
}

View File

@@ -14,6 +14,7 @@ import { computeMarchingCubesLines } from '../../../mol-geo/util/marching-cubes/
import { UnitsLinesParams, UnitsVisual, UnitsLinesVisual } from '../units-visual';
import { ElementIterator, getElementLoci, eachElement } from './util/element';
import { VisualUpdateState } from '../../util';
import { Sphere3D } from '../../../mol-math/geometry';
async function createGaussianWireframe(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, lines?: Lines): Promise<Lines> {
const { smoothness } = props
@@ -28,6 +29,9 @@ async function createGaussianWireframe(ctx: VisualContext, unit: Unit, structure
Lines.transform(wireframe, transform)
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.radiusOffset)
wireframe.setBoundingSphere(sphere)
return wireframe
}

View File

@@ -16,6 +16,7 @@ import { computeMarchingCubesMesh } from '../../../mol-geo/util/marching-cubes/a
import { ElementIterator, getElementLoci, eachElement } from './util/element';
import { VisualUpdateState } from '../../util';
import { CommonSurfaceParams } from './util/common';
import { Sphere3D } from '../../../mol-math/geometry';
export const MolecularSurfaceMeshParams = {
...UnitsMeshParams,
@@ -38,6 +39,9 @@ async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, struct
Mesh.transform(surface, transform)
if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface)
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.probeRadius)
surface.setBoundingSphere(sphere)
return surface
}

Some files were not shown because too many files have changed in this diff Show More