mirror of
https://github.com/molstar/molstar.git
synced 2026-06-07 07:04:22 +08:00
Compare commits
56 Commits
v0.6.0-dev
...
v0.6.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
112cfcac90 | ||
|
|
8016fcbd54 | ||
|
|
6de97f1d03 | ||
|
|
204075bbe0 | ||
|
|
eb448bce37 | ||
|
|
3d09b5cb67 | ||
|
|
35d06040f7 | ||
|
|
0868e81944 | ||
|
|
7efbeb7d0f | ||
|
|
815f61b550 | ||
|
|
c3a90ab499 | ||
|
|
321126afa2 | ||
|
|
9ad4a9c3c9 | ||
|
|
b12f7c7ce4 | ||
|
|
6d7d3c0794 | ||
|
|
92b988a8d5 | ||
|
|
18952ee2bd | ||
|
|
bff07888f9 | ||
|
|
8e6b0b220a | ||
|
|
74acb0d078 | ||
|
|
d4727eea02 | ||
|
|
76d14eba0c | ||
|
|
90d05d9260 | ||
|
|
23b24bbb6c | ||
|
|
91bc6f07c5 | ||
|
|
9b2181667d | ||
|
|
40347e3e46 | ||
|
|
ed277f6e16 | ||
|
|
8f2085d8b4 | ||
|
|
b5a48ad201 | ||
|
|
14b900791f | ||
|
|
de80799e68 | ||
|
|
53eca387fc | ||
|
|
fc3005f271 | ||
|
|
c95fdff00c | ||
|
|
3cd9042c72 | ||
|
|
4d3914426e | ||
|
|
af1fb7041e | ||
|
|
e2eb1bf223 | ||
|
|
5a64a6f1a3 | ||
|
|
88aa9303d7 | ||
|
|
5c77eec184 | ||
|
|
a931ed7c01 | ||
|
|
94cd2b618c | ||
|
|
9c97fc258d | ||
|
|
0ac1cfe555 | ||
|
|
3942f1bc33 | ||
|
|
a66e38a901 | ||
|
|
f65f4f4aeb | ||
|
|
f28b13bf87 | ||
|
|
8f211a0785 | ||
|
|
ba1dfb2851 | ||
|
|
7ccf36a0fa | ||
|
|
b0ee640c12 | ||
|
|
cd6d41a035 | ||
|
|
9c8f1f3e11 |
@@ -30,7 +30,7 @@ node lib/servers/model/server/server
|
||||
## From NPM
|
||||
|
||||
```
|
||||
npm install molstar
|
||||
npm install --production molstar
|
||||
./model-server
|
||||
```
|
||||
|
||||
|
||||
@@ -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
1011
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
26
package.json
26
package.json
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
@@ -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'));
|
||||
@@ -88,7 +88,6 @@ async function tryLoadFromUrl(ctx: PluginContext) {
|
||||
format: format as any,
|
||||
isBinary,
|
||||
options: params.source.params.options,
|
||||
structure: params.source.params.structure,
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 } {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
`
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
52
src/mol-math/geometry/boundary.ts
Normal file
52
src/mol-math/geometry/boundary.ts
Normal 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 };
|
||||
}
|
||||
@@ -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 }
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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[] = [
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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.`);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
17
src/mol-plugin-state/builder/preset-provider.ts
Normal file
17
src/mol-plugin-state/builder/preset-provider.ts
Normal 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
|
||||
}
|
||||
@@ -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);
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
117
src/mol-plugin-state/builder/structure/hierarchy-preset.ts
Normal file
117
src/mol-plugin-state/builder/structure/hierarchy-preset.ts
Normal 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);
|
||||
}
|
||||
@@ -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; }
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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 }) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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`;
|
||||
}
|
||||
}
|
||||
@@ -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 } });
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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()}
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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>}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -195,7 +195,8 @@
|
||||
stroke: $font-color;
|
||||
}
|
||||
|
||||
.msp-no-overflow {
|
||||
.msp-no-overflow {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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} />;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -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' && <>
|
||||
|
||||
@@ -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>
|
||||
</>}
|
||||
|
||||
@@ -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}
|
||||
</>;
|
||||
}
|
||||
}
|
||||
74
src/mol-plugin-ui/structure/unitcell.tsx
Normal file
74
src/mol-plugin-ui/structure/unitcell.tsx
Normal 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>
|
||||
</>}
|
||||
</>;
|
||||
}
|
||||
}
|
||||
@@ -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],
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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[] = []
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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: [] })
|
||||
|
||||
@@ -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 {
|
||||
|
||||
100
src/mol-repr/shape/model/unitcell.ts
Normal file
100
src/mol-repr/shape/model/unitcell.ts
Normal 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>)
|
||||
}
|
||||
@@ -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 })
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user