mirror of
https://github.com/molstar/molstar.git
synced 2026-06-06 06:34:23 +08:00
Compare commits
24 Commits
v0.6.0-dev
...
v0.6.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f2085d8b4 | ||
|
|
b5a48ad201 | ||
|
|
14b900791f | ||
|
|
de80799e68 | ||
|
|
53eca387fc | ||
|
|
fc3005f271 | ||
|
|
c95fdff00c | ||
|
|
3cd9042c72 | ||
|
|
4d3914426e | ||
|
|
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.4",
|
||||
"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",
|
||||
|
||||
@@ -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
|
||||
@@ -397,6 +398,11 @@ namespace Canvas3D {
|
||||
requestDraw(true)
|
||||
reprCount.next(reprRenderObjects.size)
|
||||
},
|
||||
syncVisibility: () => {
|
||||
if (scene.syncVisibility()) {
|
||||
camera.setState({ radiusMax: scene.boundingSphere.radius })
|
||||
}
|
||||
},
|
||||
|
||||
// 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)
|
||||
}
|
||||
@@ -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])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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 } {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,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 +62,6 @@ const ShaderChunks: { [k: string]: string } = {
|
||||
assign_group,
|
||||
assign_marker_varying,
|
||||
assign_material_color,
|
||||
assign_normal,
|
||||
assign_position,
|
||||
assign_size,
|
||||
check_picking_alpha,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ import { Download, ParsePsf } from '../transforms/data';
|
||||
import { CoordinatesFromDcd, CustomModelProperties, CustomStructureProperties, TopologyFromPsf, TrajectoryFromModelAndCoordinates } from '../transforms/model';
|
||||
import { DataFormatProvider, guessCifVariant } from './data-format';
|
||||
|
||||
// TODO make unitcell creation part of preset
|
||||
|
||||
export const MmcifProvider: DataFormatProvider<PluginStateObject.Data.String | PluginStateObject.Data.Binary> = {
|
||||
label: 'mmCIF',
|
||||
description: 'mmCIF',
|
||||
@@ -32,9 +34,10 @@ export const MmcifProvider: DataFormatProvider<PluginStateObject.Data.String | P
|
||||
},
|
||||
getDefaultBuilder: (ctx: PluginContext, data, options) => {
|
||||
return Task.create('mmCIF default builder', async taskCtx => {
|
||||
const { structure } = await ctx.builders.structure.parseStructure({ data, dataFormat: 'mmcif' });
|
||||
const { structure, model } = await ctx.builders.structure.parseStructure({ data, dataFormat: 'mmcif' });
|
||||
if (options.visuals) {
|
||||
await ctx.builders.structure.representation.applyPreset(structure, 'auto');
|
||||
await ctx.builders.structure.createUnitcell(model, undefined, { isHidden: true })
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -50,9 +53,10 @@ 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' });
|
||||
const { structure, model } = await ctx.builders.structure.parseStructure({ data, dataFormat: 'pdb' });
|
||||
if (options.visuals) {
|
||||
await ctx.builders.structure.representation.applyPreset(structure, 'auto');
|
||||
await ctx.builders.structure.createUnitcell(model, undefined, { isHidden: true })
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -68,9 +72,10 @@ 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' });
|
||||
const { structure, model } = await ctx.builders.structure.parseStructure({ data, dataFormat: 'gro' });
|
||||
if (options.visuals) {
|
||||
await ctx.builders.structure.representation.applyPreset(structure, 'auto');
|
||||
await ctx.builders.structure.createUnitcell(model, undefined, { isHidden: true })
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -86,9 +91,10 @@ 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' });
|
||||
const { structure, model } = await ctx.builders.structure.parseStructure({ data, dataFormat: '3dg' });
|
||||
if (options.visuals) {
|
||||
await ctx.builders.structure.representation.applyPreset(structure, 'auto');
|
||||
await ctx.builders.structure.createUnitcell(model, undefined, { isHidden: true })
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -238,8 +244,8 @@ const DownloadStructure = StateAction.build({
|
||||
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({
|
||||
}, { state: { isGhost: true } });
|
||||
const { structure, model } = await plugin.builders.structure.parseStructure({
|
||||
blob,
|
||||
blobParams: { formats: downloadParams.map((_, i) => ({ id: '' + i, format: 'cif' as 'cif' })) },
|
||||
modelProperties: supportProps,
|
||||
@@ -247,11 +253,12 @@ const DownloadStructure = StateAction.build({
|
||||
});
|
||||
if (createRepr) {
|
||||
await plugin.builders.structure.representation.applyPreset(structure, 'auto');
|
||||
await plugin.builders.structure.createUnitcell(model, undefined, { isHidden: true })
|
||||
}
|
||||
} else {
|
||||
for (const download of downloadParams) {
|
||||
const data = await plugin.builders.data.download(download, { state: { isGhost: true } });
|
||||
const { structure } = await plugin.builders.structure.parseStructure({
|
||||
const { structure, model } = await plugin.builders.structure.parseStructure({
|
||||
data,
|
||||
dataFormat: format,
|
||||
modelProperties: supportProps,
|
||||
@@ -259,6 +266,7 @@ const DownloadStructure = StateAction.build({
|
||||
});
|
||||
if (createRepr) {
|
||||
await plugin.builders.structure.representation.applyPreset(structure, 'auto');
|
||||
await plugin.builders.structure.createUnitcell(model, undefined, { isHidden: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -21,7 +21,9 @@ 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,7 +45,7 @@ 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;
|
||||
}
|
||||
@@ -60,18 +62,18 @@ export class StructureBuilder {
|
||||
structure?: RootStructureDefinition.Params,
|
||||
structureProperties?: boolean | StateTransformer.Params<StateTransforms['Model']['CustomStructureProperties']>
|
||||
}) {
|
||||
const trajectory = params.data
|
||||
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
|
||||
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
|
||||
const structureProperties = !!params.structureProperties
|
||||
? await this.insertStructureProperties(structure, typeof params?.structureProperties !== 'boolean' ? params?.structureProperties : void 0) : void 0;
|
||||
|
||||
|
||||
return {
|
||||
trajectory,
|
||||
model: modelProperties || model,
|
||||
@@ -113,10 +115,19 @@ export class StructureBuilder {
|
||||
return props.selector;
|
||||
}
|
||||
|
||||
async createUnitcell(model: StateObjectRef<SO.Molecule.Model>, params?: StateTransformer.Params<StateTransforms['Representation']['ModelUnitcell3D']>, initialState?: Partial<StateTransform.State>) {
|
||||
const state = this.plugin.state.data;
|
||||
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(model: StateObjectRef<SO.Molecule.Model>, params?: RootStructureDefinition.Params, 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 });
|
||||
.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 +152,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,7 +169,7 @@ export class StructureBuilder {
|
||||
return selector;
|
||||
}
|
||||
|
||||
tryCreateStaticComponent(params: { structure: StateObjectRef<SO.Molecule.Structure>, type: StaticStructureComponentType, key: string, label?: string, tags?: string[] }) {
|
||||
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 },
|
||||
nullIfEmpty: true,
|
||||
@@ -168,13 +179,13 @@ export class StructureBuilder {
|
||||
|
||||
tryCreateQueryComponent(params: { structure: StateObjectRef<SO.Molecule.Structure>, query: StructureSelectionQuery, key: string, 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 { structure, query, key, label, tags } = params;
|
||||
label = (label || '').trim();
|
||||
|
||||
const structureData = StateObjectRef.resolveAndCheck(this.dataState, structure)?.obj?.data;
|
||||
|
||||
if (!structureData) return;
|
||||
|
||||
|
||||
const transformParams: StructureComponentParams = query.referencesCurrent
|
||||
? {
|
||||
type: {
|
||||
@@ -191,25 +202,25 @@ export class StructureBuilder {
|
||||
if (query.ensureCustomProperties) {
|
||||
await query.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, {
|
||||
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 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)
|
||||
}
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ 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;
|
||||
@@ -137,12 +137,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 +157,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 +192,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];
|
||||
@@ -214,19 +214,19 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
|
||||
|
||||
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();
|
||||
|
||||
|
||||
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
|
||||
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
|
||||
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 => {
|
||||
update.to(repr.cell).update(prev => {
|
||||
if (colorTheme) prev.colorTheme = colorTheme;
|
||||
if (sizeTheme) prev.sizeTheme = sizeTheme;
|
||||
});
|
||||
@@ -260,7 +260,7 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
|
||||
|
||||
const componentKey = UUID.create22();
|
||||
for (const s of xs) {
|
||||
const component = await this.plugin.builders.structure.tryCreateQueryComponent({
|
||||
const component = await this.plugin.builders.structure.tryCreateQueryComponent({
|
||||
structure: s.childRoot,
|
||||
query: params.selection,
|
||||
key: componentKey,
|
||||
@@ -297,11 +297,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 +346,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,7 +389,7 @@ 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,
|
||||
|
||||
@@ -15,7 +15,7 @@ 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);
|
||||
// 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 };
|
||||
@@ -38,9 +38,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 +48,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 +57,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 +74,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,
|
||||
@@ -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);
|
||||
|
||||
@@ -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 } });
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -246,9 +246,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 +282,7 @@ class StructureComponentGroup extends PurePluginUIComponent<{ group: StructureCo
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
selectAction: ActionMenu.OnSelect = item => {
|
||||
if (!item) return;
|
||||
this.setState({ action: void 0 });
|
||||
@@ -333,15 +333,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,
|
||||
@@ -197,7 +198,7 @@ export class StructureSourceControls extends CollapsableControls<{}, StructureSo
|
||||
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();
|
||||
if (!this.props.cell.state.isHidden) {
|
||||
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;
|
||||
|
||||
return <>
|
||||
<div className='msp-btn-row-group' style={{ marginTop: '3px' }}>
|
||||
<button className='msp-form-control msp-control-button-label' title={`Unitcell. Click to focus.`} onClick={this.focus} onMouseEnter={this.highlight} onMouseLeave={this.clearHighlight} style={{ textAlign: 'left' }}>
|
||||
Unitcell
|
||||
</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],
|
||||
|
||||
@@ -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: [] })
|
||||
|
||||
120
src/mol-repr/shape/model/unitcell.ts
Normal file
120
src/mol-repr/shape/model/unitcell.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 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 { radToDeg } from '../../../mol-math/misc';
|
||||
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 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)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
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>)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import { computeMarchingCubesLines } from '../../../mol-geo/util/marching-cubes/
|
||||
import { ElementIterator, getElementLoci, eachElement } from './util/element';
|
||||
import { VisualUpdateState } from '../../util';
|
||||
import { CommonSurfaceParams } from './util/common';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
|
||||
export const MolecularSurfaceWireframeParams = {
|
||||
...UnitsLinesParams,
|
||||
@@ -38,6 +39,9 @@ async function createMolecularSurfaceWireframe(ctx: VisualContext, unit: Unit, s
|
||||
|
||||
Lines.transform(wireframe, transform)
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.probeRadius)
|
||||
wireframe.setBoundingSphere(sphere)
|
||||
|
||||
return wireframe
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../units-visual';
|
||||
import { NucleotideLocationIterator, getNucleotideElementLoci, eachNucleotideElement } from './util/nucleotide';
|
||||
import { VisualUpdateState } from '../../util';
|
||||
import { BaseGeometry } from '../../../mol-geo/geometry/base';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
|
||||
const p1 = Vec3.zero()
|
||||
const p2 = Vec3.zero()
|
||||
@@ -134,7 +135,12 @@ function createNucleotideBlockMesh(ctx: VisualContext, unit: Unit, structure: St
|
||||
}
|
||||
}
|
||||
|
||||
return MeshBuilder.getMesh(builderState)
|
||||
const m = MeshBuilder.getMesh(builderState)
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * props.sizeFactor)
|
||||
m.setBoundingSphere(sphere)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
export const NucleotideBlockParams = {
|
||||
|
||||
@@ -21,6 +21,7 @@ import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../units-visual';
|
||||
import { NucleotideLocationIterator, getNucleotideElementLoci, eachNucleotideElement } from './util/nucleotide';
|
||||
import { VisualUpdateState } from '../../util';
|
||||
import { BaseGeometry } from '../../../mol-geo/geometry/base';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
|
||||
const pTrace = Vec3.zero()
|
||||
const pN1 = Vec3.zero()
|
||||
@@ -189,7 +190,12 @@ function createNucleotideRingMesh(ctx: VisualContext, unit: Unit, structure: Str
|
||||
}
|
||||
}
|
||||
|
||||
return MeshBuilder.getMesh(builderState)
|
||||
const m = MeshBuilder.getMesh(builderState)
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * props.sizeFactor)
|
||||
m.setBoundingSphere(sphere)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
export const NucleotideRingParams = {
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
|
||||
import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { addEllipsoid } from '../../../mol-geo/geometry/mesh/builder/ellipsoid';
|
||||
import { Axes3D } from '../../../mol-math/geometry';
|
||||
import { Axes3D, Sphere3D } from '../../../mol-math/geometry';
|
||||
import { PickingId } from '../../../mol-geo/geometry/picking';
|
||||
import { OrderedSet, Interval } from '../../../mol-data/int';
|
||||
import { EmptyLoci, Loci } from '../../../mol-model/loci';
|
||||
@@ -82,7 +82,12 @@ export function createOrientationEllipsoidMesh(ctx: VisualContext, unit: Unit, s
|
||||
builderState.currentGroup = 0
|
||||
addEllipsoid(builderState, origin, dirA, dirB, radiusScale, detail)
|
||||
|
||||
return MeshBuilder.getMesh(builderState)
|
||||
const m = MeshBuilder.getMesh(builderState)
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * props.sizeFactor)
|
||||
m.setBoundingSphere(sphere)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -19,6 +19,7 @@ import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../units-visual';
|
||||
import { ElementIterator, getElementLoci, eachElement } from './util/element';
|
||||
import { VisualUpdateState } from '../../util';
|
||||
import { BaseGeometry } from '../../../mol-geo/geometry/base';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
|
||||
export const PolymerBackboneCylinderParams = {
|
||||
sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
|
||||
@@ -58,7 +59,12 @@ function createPolymerBackboneCylinderMesh(ctx: VisualContext, unit: Unit, struc
|
||||
addCylinder(builderState, pB, pA, 0.5, cylinderProps)
|
||||
}
|
||||
|
||||
return MeshBuilder.getMesh(builderState)
|
||||
const m = MeshBuilder.getMesh(builderState)
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * props.sizeFactor)
|
||||
m.setBoundingSphere(sphere)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
export const PolymerBackboneParams = {
|
||||
|
||||
@@ -16,6 +16,7 @@ import { createCurveSegmentState, PolymerTraceIterator, interpolateCurveSegment,
|
||||
import { isNucleic, SecondaryStructureType } from '../../../mol-model/structure/model/types';
|
||||
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../units-visual';
|
||||
import { VisualUpdateState } from '../../util';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
|
||||
const t = Mat4.identity()
|
||||
const sVec = Vec3.zero()
|
||||
@@ -82,7 +83,12 @@ function createPolymerDirectionWedgeMesh(ctx: VisualContext, unit: Unit, structu
|
||||
++i
|
||||
}
|
||||
|
||||
return MeshBuilder.getMesh(builderState)
|
||||
const m = MeshBuilder.getMesh(builderState)
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * props.sizeFactor)
|
||||
m.setBoundingSphere(sphere)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
export const PolymerDirectionParams = {
|
||||
|
||||
@@ -17,6 +17,7 @@ import { addFixedCountDashedCylinder } from '../../../mol-geo/geometry/mesh/buil
|
||||
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../units-visual';
|
||||
import { VisualUpdateState } from '../../util';
|
||||
import { BaseGeometry } from '../../../mol-geo/geometry/base';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
// import { TriangularPyramid } from '../../../mol-geo/primitive/pyramid';
|
||||
|
||||
const segmentCount = 10
|
||||
@@ -77,7 +78,12 @@ function createPolymerGapCylinderMesh(ctx: VisualContext, unit: Unit, structure:
|
||||
i += 2
|
||||
}
|
||||
|
||||
return MeshBuilder.getMesh(builderState)
|
||||
const m = MeshBuilder.getMesh(builderState)
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * props.sizeFactor)
|
||||
m.setBoundingSphere(sphere)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
export const PolymerGapParams = {
|
||||
|
||||
@@ -21,6 +21,7 @@ import { addRibbon } from '../../../mol-geo/geometry/mesh/builder/ribbon';
|
||||
import { addSphere } from '../../../mol-geo/geometry/mesh/builder/sphere';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { BaseGeometry } from '../../../mol-geo/geometry/base';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
|
||||
export const PolymerTraceMeshParams = {
|
||||
sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }),
|
||||
@@ -147,7 +148,12 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
|
||||
++i
|
||||
}
|
||||
|
||||
return MeshBuilder.getMesh(builderState)
|
||||
const m = MeshBuilder.getMesh(builderState)
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * props.sizeFactor)
|
||||
m.setBoundingSphere(sphere)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
export const PolymerTraceParams = {
|
||||
|
||||
@@ -20,6 +20,7 @@ import { addRibbon } from '../../../mol-geo/geometry/mesh/builder/ribbon';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { addSphere } from '../../../mol-geo/geometry/mesh/builder/sphere';
|
||||
import { BaseGeometry } from '../../../mol-geo/geometry/base';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
|
||||
export const PolymerTubeMeshParams = {
|
||||
sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }),
|
||||
@@ -100,7 +101,12 @@ function createPolymerTubeMesh(ctx: VisualContext, unit: Unit, structure: Struct
|
||||
++i
|
||||
}
|
||||
|
||||
return MeshBuilder.getMesh(builderState)
|
||||
const m = MeshBuilder.getMesh(builderState)
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * props.sizeFactor)
|
||||
m.setBoundingSphere(sphere)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
export const PolymerTubeParams = {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { AtomicNumbers, AtomNumber } from '../../../../mol-model/structure/model
|
||||
import { fillSerial } from '../../../../mol-util/array';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { AssignableArrayLike } from '../../../../mol-util/type-helpers';
|
||||
import { getBoundary } from '../../../../mol-math/geometry/boundary';
|
||||
|
||||
/** Return a Loci for the elements of a whole residue the elementIndex belongs to. */
|
||||
export function getResidueLoci(structure: Structure, unit: Unit.Atomic, elementIndex: ElementIndex): Loci {
|
||||
@@ -139,7 +140,7 @@ export function getUnitConformationAndRadius(structure: Structure, unit: Unit, p
|
||||
|
||||
const { x, y, z } = getConformation(rootUnit)
|
||||
const { elements } = rootUnit
|
||||
const { center, radius: sphereRadius } = unit.lookup3d.boundary.sphere
|
||||
const { center, radius: sphereRadius } = unit.boundary.sphere
|
||||
const extraRadius = (2 + 1.5) * 2 // TODO should be twice (the max vdW/sphere radius plus the probe radius)
|
||||
const radiusSq = (sphereRadius + extraRadius) * (sphereRadius + extraRadius)
|
||||
|
||||
@@ -169,6 +170,7 @@ export function getUnitConformationAndRadius(structure: Structure, unit: Unit, p
|
||||
}
|
||||
|
||||
const position = { indices, x, y, z, id }
|
||||
const boundary = unit === rootUnit ? unit.boundary : getBoundary(position)
|
||||
|
||||
const l = StructureElement.Location.create(structure, rootUnit)
|
||||
const sizeTheme = PhysicalSizeTheme({}, {})
|
||||
@@ -177,7 +179,7 @@ export function getUnitConformationAndRadius(structure: Structure, unit: Unit, p
|
||||
return sizeTheme.size(l)
|
||||
}
|
||||
|
||||
return { position, radius }
|
||||
return { position, boundary, radius }
|
||||
}
|
||||
|
||||
export function getStructureConformationAndRadius(structure: Structure, ignoreHydrogens: boolean) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import { StructureGroup } from '../../../../mol-repr/structure/units-visual';
|
||||
import { Spheres } from '../../../../mol-geo/geometry/spheres/spheres';
|
||||
import { SpheresBuilder } from '../../../../mol-geo/geometry/spheres/spheres-builder';
|
||||
import { isHydrogen } from './common';
|
||||
import { Sphere3D } from '../../../../mol-math/geometry';
|
||||
|
||||
export interface ElementSphereMeshProps {
|
||||
detail: number,
|
||||
@@ -51,7 +52,12 @@ export function createElementSphereMesh(ctx: VisualContext, unit: Unit, structur
|
||||
addSphere(builderState, v, theme.size.size(l) * sizeFactor, detail)
|
||||
}
|
||||
|
||||
return MeshBuilder.getMesh(builderState)
|
||||
const m = MeshBuilder.getMesh(builderState)
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * sizeFactor)
|
||||
m.setBoundingSphere(sphere)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
export interface ElementSphereImpostorProps {
|
||||
@@ -74,7 +80,12 @@ export function createElementSphereImpostor(ctx: VisualContext, unit: Unit, stru
|
||||
builder.add(v[0], v[1], v[2], i)
|
||||
}
|
||||
|
||||
return builder.getSpheres()
|
||||
const s = builder.getSpheres()
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1)
|
||||
s.setBoundingSphere(sphere)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
export function eachElement(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {
|
||||
|
||||
@@ -10,12 +10,13 @@ import { getUnitConformationAndRadius, CommonSurfaceProps } from './common';
|
||||
import { PositionData, DensityData, Box3D } from '../../../../mol-math/geometry';
|
||||
import { MolecularSurfaceCalculationProps, calcMolecularSurface } from '../../../../mol-math/geometry/molecular-surface';
|
||||
import { OrderedSet } from '../../../../mol-data/int';
|
||||
import { Boundary } from '../../../../mol-math/geometry/boundary';
|
||||
|
||||
export type MolecularSurfaceProps = MolecularSurfaceCalculationProps & CommonSurfaceProps
|
||||
|
||||
function getPositionDataAndMaxRadius(structure: Structure, unit: Unit, props: MolecularSurfaceProps) {
|
||||
const { probeRadius } = props
|
||||
const { position, radius } = getUnitConformationAndRadius(structure, unit, props)
|
||||
const { position, boundary, radius } = getUnitConformationAndRadius(structure, unit, props)
|
||||
const { indices } = position
|
||||
const n = OrderedSet.size(indices)
|
||||
const radii = new Float32Array(OrderedSet.end(indices))
|
||||
@@ -28,19 +29,19 @@ function getPositionDataAndMaxRadius(structure: Structure, unit: Unit, props: Mo
|
||||
radii[j] = r + probeRadius
|
||||
}
|
||||
|
||||
return { position: { ...position, radius: radii }, maxRadius }
|
||||
return { position: { ...position, radius: radii }, boundary, maxRadius }
|
||||
}
|
||||
|
||||
export function computeUnitMolecularSurface(structure: Structure, unit: Unit, props: MolecularSurfaceProps) {
|
||||
const { box } = unit.lookup3d.boundary
|
||||
const { position, maxRadius } = getPositionDataAndMaxRadius(structure, unit, props)
|
||||
const { position, boundary, maxRadius } = getPositionDataAndMaxRadius(structure, unit, props)
|
||||
return Task.create('Molecular Surface', async ctx => {
|
||||
return await MolecularSurface(ctx, position, maxRadius, box, props);
|
||||
return await MolecularSurface(ctx, position, boundary, maxRadius, box, props);
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
async function MolecularSurface(ctx: RuntimeContext, position: Required<PositionData>, maxRadius: number, box: Box3D | null, props: MolecularSurfaceCalculationProps): Promise<DensityData> {
|
||||
return calcMolecularSurface(ctx, position, maxRadius, box, props)
|
||||
async function MolecularSurface(ctx: RuntimeContext, position: Required<PositionData>, boundary: Boundary, maxRadius: number, box: Box3D | null, props: MolecularSurfaceCalculationProps): Promise<DensityData> {
|
||||
return calcMolecularSurface(ctx, position, boundary, maxRadius, box, props)
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
export type Mutable<T> = { -readonly [P in keyof T]: T[P] }
|
||||
export type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
|
||||
export type PickRequired<T, K extends keyof T> = T & Required<Pick<T, K>>
|
||||
|
||||
export type TypedIntArray = Int8Array | Int16Array | Int32Array | Uint8Array | Uint16Array | Uint32Array
|
||||
export type TypedFloatArray = Float32Array | Float64Array
|
||||
|
||||
@@ -8,6 +8,7 @@ import { GridLookup3D } from '../mol-math/geometry';
|
||||
// import { sortArray } from 'mol-data/util';
|
||||
import { OrderedSet } from '../mol-data/int';
|
||||
import { trajectoryFromMmCIF, MmcifFormat } from '../mol-model-formats/structure/mmcif';
|
||||
import { getBoundary } from '../mol-math/geometry/boundary';
|
||||
|
||||
require('util.promisify').shim();
|
||||
const readFileAsync = util.promisify(fs.readFile);
|
||||
@@ -41,11 +42,12 @@ export async function readCIF(path: string) {
|
||||
export async function test() {
|
||||
const { mmcif, structures } = await readCIF('e:/test/quick/1tqn_updated.cif');
|
||||
|
||||
const lookup = GridLookup3D({ x: mmcif.db.atom_site.Cartn_x.toArray(), y: mmcif.db.atom_site.Cartn_y.toArray(), z: mmcif.db.atom_site.Cartn_z.toArray(),
|
||||
const position = { x: mmcif.db.atom_site.Cartn_x.toArray(), y: mmcif.db.atom_site.Cartn_y.toArray(), z: mmcif.db.atom_site.Cartn_z.toArray(),
|
||||
indices: OrderedSet.ofBounds(0, mmcif.db.atom_site._rowCount),
|
||||
// radius: [1, 1, 1, 1]
|
||||
// indices: [1]
|
||||
});
|
||||
}
|
||||
const lookup = GridLookup3D(position, getBoundary(position));
|
||||
console.log(lookup.boundary.box, lookup.boundary.sphere);
|
||||
|
||||
const result = lookup.find(-30.07, 8.178, -13.897, 10);
|
||||
|
||||
@@ -2,7 +2,6 @@ const common = require('./webpack.config.common.js');
|
||||
const createApp = common.createApp;
|
||||
const createEntry = common.createEntry;
|
||||
const createBrowserTest = common.createBrowserTest;
|
||||
const createNodeEntryPoint = common.createNodeEntryPoint;
|
||||
const createNodeApp = common.createNodeApp;
|
||||
|
||||
module.exports = [
|
||||
@@ -11,7 +10,6 @@ module.exports = [
|
||||
createEntry('examples/proteopedia-wrapper/index', 'examples/proteopedia-wrapper', 'index'),
|
||||
createEntry('apps/demos/lighting/index', 'demos/lighting', 'index'),
|
||||
createNodeApp('state-docs'),
|
||||
createNodeEntryPoint('preprocess', 'servers/model', 'model-server'),
|
||||
createApp('model-server-query'),
|
||||
|
||||
createBrowserTest('font-atlas'),
|
||||
|
||||
Reference in New Issue
Block a user