mirror of
https://github.com/molstar/molstar.git
synced 2026-06-08 07:54:28 +08:00
Compare commits
105 Commits
v0.6.0-dev
...
v0.6.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
472def49f6 | ||
|
|
90549893e3 | ||
|
|
73a8e45202 | ||
|
|
35df55cb4f | ||
|
|
c2028d20a8 | ||
|
|
eaffdc6a98 | ||
|
|
0b1e6100a9 | ||
|
|
2168905c11 | ||
|
|
f955e6a299 | ||
|
|
98f3981e12 | ||
|
|
82f94d20ea | ||
|
|
fbc6d47117 | ||
|
|
6a2e4cf813 | ||
|
|
4aabef7683 | ||
|
|
74ae91ee1b | ||
|
|
8b62766474 | ||
|
|
2995504916 | ||
|
|
63f6848d26 | ||
|
|
988e429693 | ||
|
|
ba1c6ef046 | ||
|
|
7ceff92a4e | ||
|
|
1f968b2836 | ||
|
|
d97d7e3b14 | ||
|
|
5c4c4811e4 | ||
|
|
f2a6e63a20 | ||
|
|
1aace4a26f | ||
|
|
b1c140d23e | ||
|
|
ee16212c31 | ||
|
|
f6d232b1c5 | ||
|
|
c8a002933e | ||
|
|
4757ca9913 | ||
|
|
5264c75e37 | ||
|
|
032bf44863 | ||
|
|
2f4f5e43f3 | ||
|
|
7b1edcadf6 | ||
|
|
f0f74d182d | ||
|
|
59142adbbc | ||
|
|
2fda8c5db1 | ||
|
|
7b5efa3e42 | ||
|
|
d784d202bd | ||
|
|
8833474a43 | ||
|
|
57cbb2f8b6 | ||
|
|
b6112a914f | ||
|
|
2008f8538c | ||
|
|
09fba43a1c | ||
|
|
b76c3613f9 | ||
|
|
cf4ddcb587 | ||
|
|
696106f48b | ||
|
|
fb286cd9cf | ||
|
|
5121bd700e | ||
|
|
6173520ad0 | ||
|
|
a7189232dd | ||
|
|
4a96b45b04 | ||
|
|
20ac549dd6 | ||
|
|
38be00c0b7 | ||
|
|
0a9bdc8cf6 | ||
|
|
0b4318280a | ||
|
|
b1da60e1c0 | ||
|
|
2cc5987f9e | ||
|
|
745415a1d8 | ||
|
|
c80658f368 | ||
|
|
033675a417 | ||
|
|
3dd57e9dc8 | ||
|
|
1e3daa6c98 | ||
|
|
8855f51cfc | ||
|
|
50d8debb2b | ||
|
|
e682eb78b0 | ||
|
|
bd2bbf3e2d | ||
|
|
f38f040aea | ||
|
|
f912b2d802 | ||
|
|
0ad03e6a0b | ||
|
|
160d8c529e | ||
|
|
28edfd810c | ||
|
|
7b931cfb66 | ||
|
|
5575c61577 | ||
|
|
8f93dce105 | ||
|
|
0eb3d7226a | ||
|
|
a9533b666c | ||
|
|
6d67b4db56 | ||
|
|
1b5eff6454 | ||
|
|
83fb28cc9d | ||
|
|
c39ffc0b0e | ||
|
|
23892cfbdd | ||
|
|
3bdabc444d | ||
|
|
c233be4467 | ||
|
|
8468f33803 | ||
|
|
0e77369fdb | ||
|
|
9fcc8e7977 | ||
|
|
538371ced8 | ||
|
|
380887bd22 | ||
|
|
17b4b1cb86 | ||
|
|
3e7c358c07 | ||
|
|
05b592a173 | ||
|
|
e1d0515fae | ||
|
|
d4d3b9645e | ||
|
|
d12d99dcfa | ||
|
|
174324d21c | ||
|
|
26156a5982 | ||
|
|
af5ddf6950 | ||
|
|
2ef35e5fb9 | ||
|
|
513bfeaae7 | ||
|
|
7311e6f484 | ||
|
|
90ecedcae8 | ||
|
|
352a20ac48 | ||
|
|
b1d8c5f6ea |
3457
package-lock.json
generated
3457
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "0.6.0-dev.7",
|
||||
"version": "0.6.0-dev.9",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -21,7 +21,7 @@
|
||||
"watch-viewer": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer\"",
|
||||
"watch-tsc": "tsc --watch --incremental",
|
||||
"watch-extra": "cpx \"src/**/*.{scss,woff,woff2,ttf,otf,eot,svg,html,ico}\" lib/ --watch",
|
||||
"watch-webpack": "webpack -w --mode development --display errors-only --info-verbosity verbose",
|
||||
"watch-webpack": "webpack -w --mode development --display minimal",
|
||||
"watch-webpack-viewer": "webpack -w --mode development --display errors-only --info-verbosity verbose --config ./webpack.config.viewer.js",
|
||||
"serve": "http-server -p 1338",
|
||||
"model-server": "node lib/servers/model/server.js",
|
||||
@@ -82,8 +82,8 @@
|
||||
"@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.24.0",
|
||||
"@typescript-eslint/parser": "^2.24.0",
|
||||
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
||||
"@typescript-eslint/parser": "^2.26.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"circular-dependency-plugin": "^5.2.0",
|
||||
"concurrently": "^5.1.0",
|
||||
@@ -92,9 +92,9 @@
|
||||
"eslint": "^6.8.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^6.0.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
"http-server": "^0.12.1",
|
||||
"jest": "^25.1.0",
|
||||
"jest": "^25.2.4",
|
||||
"jest-raw-loader": "^1.0.1",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"node-sass": "^4.13.1",
|
||||
@@ -104,9 +104,9 @@
|
||||
"sass-loader": "^8.0.2",
|
||||
"simple-git": "^1.132.0",
|
||||
"style-loader": "^1.1.3",
|
||||
"ts-jest": "^25.2.1",
|
||||
"ts-jest": "^25.3.0",
|
||||
"typescript": "^3.8.3",
|
||||
"webpack": "^4.42.0",
|
||||
"webpack": "^4.42.1",
|
||||
"webpack-cli": "^3.3.11"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -115,9 +115,9 @@
|
||||
"@types/compression": "1.7.0",
|
||||
"@types/express": "^4.17.3",
|
||||
"@types/jest": "^25.1.4",
|
||||
"@types/node": "^13.9.2",
|
||||
"@types/node": "^13.9.8",
|
||||
"@types/node-fetch": "^2.5.5",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react": "^16.9.29",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"@types/swagger-ui-dist": "3.0.5",
|
||||
"argparse": "^1.0.10",
|
||||
@@ -129,8 +129,8 @@
|
||||
"immer": "^6.0.2",
|
||||
"immutable": "^3.8.2",
|
||||
"node-fetch": "^2.6.0",
|
||||
"react": "^16.13.0",
|
||||
"react-dom": "^16.13.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"rxjs": "^6.5.4",
|
||||
"swagger-ui-dist": "^3.25.0",
|
||||
"tslib": "^1.11.1",
|
||||
|
||||
@@ -58,12 +58,14 @@ export namespace StateHelper {
|
||||
|
||||
export function identityTransform(b: StateBuilder.To<PSO.Molecule.Structure>, m: Mat4) {
|
||||
return b.apply(StateTransforms.Model.TransformStructureConformation,
|
||||
{ axis: Vec3.create(1, 0, 0), angle: 0, translation: Vec3.zero() },
|
||||
{ transform: { name: 'components', params: { axis: Vec3.create(1, 0, 0), angle: 0, translation: Vec3.zero() } } },
|
||||
{ tags: 'transform' });
|
||||
}
|
||||
|
||||
export function transform(b: StateBuilder.To<PSO.Molecule.Structure>, matrix: Mat4) {
|
||||
return b.apply(StateTransforms.Model.TransformStructureConformationByMatrix, { matrix }, { tags: 'transform' });
|
||||
return b.apply(StateTransforms.Model.TransformStructureConformation, {
|
||||
transform: { name: 'matrix', params: matrix }
|
||||
}, { tags: 'transform' });
|
||||
}
|
||||
|
||||
export function assemble(b: StateBuilder.To<PSO.Molecule.Model>, id?: string) {
|
||||
|
||||
@@ -19,7 +19,7 @@ function paramInfo(param: PD.Any, offset: number): string {
|
||||
case 'conditioned': return getParams(param.conditionParams, offset);
|
||||
case 'multi-select': return `Array of ${oToS(param.options)}`;
|
||||
case 'color': return 'Color as 0xrrggbb';
|
||||
case 'color-list': return `One of ${oToS(param.options)}`;
|
||||
case 'color-list': return `A list of colors as 0xrrggbb`;
|
||||
case 'vec3': return `3D vector [x, y, z]`;
|
||||
case 'file': return `JavaScript File Handle`;
|
||||
case 'file-list': return `JavaScript FileList Handle`;
|
||||
@@ -39,7 +39,7 @@ function paramInfo(param: PD.Any, offset: number): string {
|
||||
}
|
||||
}
|
||||
|
||||
function oToS(options: readonly (readonly [string, string] | readonly [string, string, string])[]) {
|
||||
function oToS(options: readonly (readonly [string, string] | readonly [string, string, string | undefined])[]) {
|
||||
return options.map(o => `'${o[0]}'`).join(', ');
|
||||
}
|
||||
|
||||
|
||||
@@ -4,32 +4,31 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StateAction } from '../../../../mol-state';
|
||||
import { StateAction, StateBuilder, StateTransformer } from '../../../../mol-state';
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { PluginStateObject as PSO } from '../../../../mol-plugin-state/objects';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { Ingredient, CellPacking, Cell } from './data';
|
||||
import { Ingredient, CellPacking } from './data';
|
||||
import { getFromPdb, getFromCellPackDB } from './util';
|
||||
import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit } from '../../../../mol-model/structure';
|
||||
import { trajectoryFromMmCIF, MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
|
||||
import { trajectoryFromPDB } from '../../../../mol-model-formats/structure/pdb';
|
||||
import { Mat4, Vec3, Quat } from '../../../../mol-math/linear-algebra';
|
||||
import { SymmetryOperator } from '../../../../mol-math/geometry';
|
||||
import { Task } from '../../../../mol-task';
|
||||
import { Task, RuntimeContext } from '../../../../mol-task';
|
||||
import { StateTransforms } from '../../../../mol-plugin-state/transforms';
|
||||
import { distinctColors } from '../../../../mol-util/color/distinct';
|
||||
import { ModelIndexColorThemeProvider } from '../../../../mol-theme/color/model-index';
|
||||
import { Hcl } from '../../../../mol-util/color/spaces/hcl';
|
||||
import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl } from './state';
|
||||
import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder';
|
||||
import { getMatFromResamplePoints } from './curve';
|
||||
import { compile } from '../../../../mol-script/runtime/query/compiler';
|
||||
import { UniformColorThemeProvider } from '../../../../mol-theme/color/uniform';
|
||||
import { CifCategory, CifField } from '../../../../mol-io/reader/cif';
|
||||
import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
|
||||
import { Column } from '../../../../mol-data/db';
|
||||
import { createModels } from '../../../../mol-model-formats/structure/basic/parser';
|
||||
import { createStructureRepresentationParams } from '../../../../mol-plugin-state/helpers/structure-representation-params';
|
||||
import { CellpackPackingsPreset } from './preset';
|
||||
import { AjaxTask } from '../../../../mol-util/data-source';
|
||||
|
||||
function getCellPackModelUrl(fileName: string, baseUrl: string) {
|
||||
return `${baseUrl}/results/${fileName}`
|
||||
@@ -111,7 +110,7 @@ function getAssembly(transforms: Mat4[], structure: Structure) {
|
||||
|
||||
for (let i = 0, il = transforms.length; i < il; ++i) {
|
||||
const id = `${i + 1}`
|
||||
const op = SymmetryOperator.create(id, transforms[i], { id, operList: [ id ] })
|
||||
const op = SymmetryOperator.create(id, transforms[i], { assembly: { id, operId: i, operList: [ id ] } })
|
||||
for (const unit of units) {
|
||||
builder.addWithOperator(unit, op)
|
||||
}
|
||||
@@ -270,200 +269,131 @@ export function createStructureFromCellPack(packing: CellPacking, baseUrl: strin
|
||||
if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId
|
||||
builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, Unit.Trait.None, invariantId)
|
||||
}
|
||||
offsetInvariantId += maxInvariantId
|
||||
offsetInvariantId += maxInvariantId + 1
|
||||
}
|
||||
|
||||
if (ctx.shouldUpdate) await ctx.update(`${name} - structure`)
|
||||
const s = builder.getStructure()
|
||||
for( let i = 0, il = s.models.length; i < il; ++i) {
|
||||
const { trajectoryInfo } = s.models[i]
|
||||
trajectoryInfo.size = il
|
||||
trajectoryInfo.index = i
|
||||
}
|
||||
return s
|
||||
})
|
||||
}
|
||||
|
||||
const RepresentationOptions = PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'ellipsoid'] as const)
|
||||
type RepresentationName = (typeof RepresentationOptions)[0][0]
|
||||
async function handleHivRna(ctx: { runtime: RuntimeContext, fetch: AjaxTask }, packings: CellPacking[], baseUrl: string) {
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0') {
|
||||
const url = `${baseUrl}/extras/rna_allpoints.json`
|
||||
const data = await ctx.fetch({ url, type: 'string' }).runInContext(ctx.runtime);
|
||||
const { points } = await (new Response(data)).json() as { points: number[] }
|
||||
|
||||
const curve0: Vec3[] = []
|
||||
for (let j = 0, jl = points.length; j < jl; j += 3) {
|
||||
curve0.push(Vec3.fromArray(Vec3(), points, j))
|
||||
}
|
||||
packings[i].ingredients['RNA'] = {
|
||||
source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
|
||||
results: [],
|
||||
name: 'RNA',
|
||||
nbCurve: 1,
|
||||
curve0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const LoadCellPackModel = StateAction.build({
|
||||
display: { name: 'Load CellPack Model' },
|
||||
params: {
|
||||
id: PD.Select('influenza_model1.json', [
|
||||
['blood_hiv_immature_inside.json', 'blood_hiv_immature_inside'],
|
||||
['BloodHIV1.0_mixed_fixed_nc1.cpr', 'BloodHIV1.0_mixed_fixed_nc1'],
|
||||
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV-1_0.1.6-8_mixed_radii_pdb'],
|
||||
['influenza_model1.json', 'influenza_model1'],
|
||||
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
|
||||
['curveTest', 'Curve Test'],
|
||||
] as const),
|
||||
source: PD.MappedStatic('id', {
|
||||
'id': PD.Select('influenza_model1.json', [
|
||||
['blood_hiv_immature_inside.json', 'blood_hiv_immature_inside'],
|
||||
['BloodHIV1.0_mixed_fixed_nc1.cpr', 'BloodHIV1.0_mixed_fixed_nc1'],
|
||||
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV-1_0.1.6-8_mixed_radii_pdb'],
|
||||
['influenza_model1.json', 'influenza_model1'],
|
||||
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
|
||||
] as const),
|
||||
'file': PD.File({ accept: 'id' }),
|
||||
}, { options: [['id', 'Id'], ['file', 'File']] }),
|
||||
// id: PD.Select('influenza_model1.json', [
|
||||
// ['blood_hiv_immature_inside.json', 'blood_hiv_immature_inside'],
|
||||
// ['BloodHIV1.0_mixed_fixed_nc1.cpr', 'BloodHIV1.0_mixed_fixed_nc1'],
|
||||
// ['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV-1_0.1.6-8_mixed_radii_pdb'],
|
||||
// ['influenza_model1.json', 'influenza_model1'],
|
||||
// ['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
|
||||
// ['curveTest', 'Curve Test'],
|
||||
// ] as const),
|
||||
baseUrl: PD.Text(DefaultCellPackBaseUrl),
|
||||
preset: PD.Group({
|
||||
traceOnly: PD.Boolean(false),
|
||||
representation: PD.Select('spacefill', RepresentationOptions)
|
||||
representation: PD.Select('spacefill', PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'ellipsoid']))
|
||||
}, { isExpanded: true })
|
||||
},
|
||||
from: PSO.Root
|
||||
})(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
|
||||
const url = getCellPackModelUrl(params.id, params.baseUrl)
|
||||
|
||||
const root = state.build().toRoot();
|
||||
|
||||
let cellPackBuilder: any
|
||||
|
||||
if (params.id === 'curveTest') {
|
||||
const url = `${params.baseUrl}/extras/rna_allpoints.json`
|
||||
const data = await ctx.fetch({ url, type: 'string' }).runInContext(taskCtx);
|
||||
const { points } = await (new Response(data)).json() as { points: number[] }
|
||||
const curve0: Vec3[] = []
|
||||
for (let j = 0, jl = Math.min(points.length, 3 * 100); j < jl; j += 3) {
|
||||
curve0.push(Vec3.fromArray(Vec3(), points, j))
|
||||
}
|
||||
const cell: Cell = {
|
||||
recipe: { setupfile: '', paths: [], version: '', name: 'Curve Test' },
|
||||
compartments: {
|
||||
'CurveCompartment': {
|
||||
interior: {
|
||||
ingredients: {
|
||||
'CurveIngredient': {
|
||||
source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
|
||||
results: [],
|
||||
name: 'RNA',
|
||||
nbCurve: 1,
|
||||
curve0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cellPackBuilder = root
|
||||
.apply(StateTransforms.Data.ImportJson, { data: cell }, { state: { isGhost: true } })
|
||||
.apply(ParseCellPack)
|
||||
let cellPackJson: StateBuilder.To<PSO.Format.Json, StateTransformer<PSO.Data.String, PSO.Format.Json>>
|
||||
if (params.source.name === 'id') {
|
||||
const url = getCellPackModelUrl(params.source.params, params.baseUrl)
|
||||
cellPackJson = state.build().toRoot()
|
||||
.apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.source.params }, { state: { isGhost: true } })
|
||||
} else {
|
||||
cellPackBuilder = root
|
||||
.apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.id }, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
|
||||
.apply(ParseCellPack)
|
||||
|
||||
|
||||
const file = params.source.params
|
||||
cellPackJson = state.build().toRoot()
|
||||
.apply(StateTransforms.Data.ReadFile, { file, isBinary: false, label: file.name }, { state: { isGhost: true } })
|
||||
}
|
||||
|
||||
const cellPackBuilder = cellPackJson
|
||||
.apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
|
||||
.apply(ParseCellPack)
|
||||
|
||||
const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(taskCtx)
|
||||
const { packings } = cellPackObject.data
|
||||
const tree = state.build().to(cellPackBuilder.ref);
|
||||
|
||||
const isHiv = (
|
||||
params.id === 'BloodHIV1.0_mixed_fixed_nc1.cpr' ||
|
||||
params.id === 'HIV-1_0.1.6-8_mixed_radii_pdb.cpr'
|
||||
)
|
||||
// TODO make configurable
|
||||
// const isHiv = params.source.name === 'id' && (
|
||||
// params.source.params === 'BloodHIV1.0_mixed_fixed_nc1.cpr' ||
|
||||
// params.source.params === 'HIV-1_0.1.6-8_mixed_radii_pdb.cpr'
|
||||
// )
|
||||
const isHiv = false
|
||||
|
||||
if (isHiv) {
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0') {
|
||||
const url = `${params.baseUrl}/extras/rna_allpoints.json`
|
||||
const data = await ctx.fetch({ url, type: 'string' }).runInContext(taskCtx);
|
||||
const { points } = await (new Response(data)).json() as { points: number[] }
|
||||
|
||||
const curve0: Vec3[] = []
|
||||
for (let j = 0, jl = points.length; j < jl; j += 3) {
|
||||
curve0.push(Vec3.fromArray(Vec3(), points, j))
|
||||
}
|
||||
packings[i].ingredients['RNA'] = {
|
||||
source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
|
||||
results: [],
|
||||
name: 'RNA',
|
||||
nbCurve: 1,
|
||||
curve0
|
||||
}
|
||||
}
|
||||
}
|
||||
await handleHivRna({ runtime: taskCtx, fetch: ctx.fetch }, packings, params.baseUrl)
|
||||
}
|
||||
|
||||
const colors = distinctColors(packings.length)
|
||||
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
const hcl = Hcl.fromColor(Hcl(), colors[i])
|
||||
const hue = [Math.max(0, hcl[0] - 35), Math.min(360, hcl[0] + 35)] as [number, number]
|
||||
const p = { packing: i, baseUrl: params.baseUrl }
|
||||
|
||||
let cellpackTree = tree.apply(StructureFromCellpack, p)
|
||||
if (params.preset.traceOnly) {
|
||||
const expression = MS.struct.generator.atomGroups({
|
||||
'atom-test': MS.core.logic.or([
|
||||
MS.core.rel.eq([MS.ammp('label_atom_id'), 'CA']),
|
||||
MS.core.rel.eq([MS.ammp('label_atom_id'), 'P'])
|
||||
])
|
||||
})
|
||||
cellpackTree = cellpackTree.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression }, { state: { isGhost: true } }) as any
|
||||
const packing = state.build().to(cellPackBuilder.ref).apply(StructureFromCellpack, p)
|
||||
await ctx.updateDataState(packing, { revertOnError: true });
|
||||
|
||||
const packingParams = {
|
||||
traceOnly: params.preset.traceOnly,
|
||||
representation: params.preset.representation,
|
||||
hue
|
||||
}
|
||||
cellpackTree
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
createStructureRepresentationParams(ctx, Structure.Empty, {
|
||||
...getReprParams(ctx, params.preset),
|
||||
...getColorParams(hue)
|
||||
})
|
||||
)
|
||||
await CellpackPackingsPreset.apply(packing.selector, packingParams, ctx)
|
||||
}
|
||||
|
||||
if (isHiv) {
|
||||
const url = `${params.baseUrl}/membranes/hiv_lipids.bcif`
|
||||
tree.apply(StateTransforms.Data.Download, { label: 'hiv_lipids', url, isBinary: true }, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.StructureFromModel, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Misc.CreateGroup, { label: 'HIV1_envelope_Membrane' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
createStructureRepresentationParams(ctx, Structure.Empty, {
|
||||
...getReprParams(ctx, params.preset),
|
||||
color: UniformColorThemeProvider
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
console.time('cellpack')
|
||||
await state.updateTree(tree).runInContext(taskCtx);
|
||||
console.timeEnd('cellpack')
|
||||
}));
|
||||
|
||||
function getReprParams(ctx: PluginContext, params: { representation: RepresentationName, traceOnly: boolean }) {
|
||||
const { representation, traceOnly } = params
|
||||
switch (representation) {
|
||||
case 'spacefill':
|
||||
return traceOnly
|
||||
? {
|
||||
type: ctx.representation.structure.registry.get('spacefill'),
|
||||
typeParams: { sizeFactor: 2, ignoreHydrogens: true }
|
||||
} : {
|
||||
type: ctx.representation.structure.registry.get('spacefill'),
|
||||
typeParams: { ignoreHydrogens: true }
|
||||
}
|
||||
case 'gaussian-surface':
|
||||
return {
|
||||
type: ctx.representation.structure.registry.get('gaussian-surface'),
|
||||
typeParams: {
|
||||
quality: 'custom', resolution: 10, radiusOffset: 2,
|
||||
alpha: 1.0, flatShaded: false, doubleSided: false,
|
||||
ignoreHydrogens: true
|
||||
}
|
||||
}
|
||||
case 'point':
|
||||
return { type: ctx.representation.structure.registry.get('point') }
|
||||
case 'ellipsoid':
|
||||
return { type: ctx.representation.structure.registry.get('orientation') }
|
||||
}
|
||||
}
|
||||
|
||||
function getColorParams(hue: [number, number]): any {
|
||||
return {
|
||||
color: ModelIndexColorThemeProvider,
|
||||
colorParams: {
|
||||
palette: {
|
||||
name: 'generate',
|
||||
params: {
|
||||
hue, chroma: [30, 80], luminance: [15, 85],
|
||||
clusteringStepCount: 50, minSampleCount: 800,
|
||||
maxCount: 75
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
// if (isHiv) {
|
||||
// const url = `${params.baseUrl}/membranes/hiv_lipids.bcif`
|
||||
// tree.apply(StateTransforms.Data.Download, { label: 'hiv_lipids', url, isBinary: true }, { state: { isGhost: true } })
|
||||
// .apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
|
||||
// .apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
|
||||
// .apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
|
||||
// .apply(StateTransforms.Model.StructureFromModel, undefined, { state: { isGhost: true } })
|
||||
// .apply(StateTransforms.Misc.CreateGroup, { label: 'HIV1_envelope_Membrane' })
|
||||
// .apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
// createStructureRepresentationParams(ctx, Structure.Empty, {
|
||||
// ...getReprParams(ctx, params.preset),
|
||||
// color: UniformColorThemeProvider
|
||||
// })
|
||||
// )
|
||||
// }
|
||||
}));
|
||||
71
src/apps/viewer/extensions/cellpack/preset.ts
Normal file
71
src/apps/viewer/extensions/cellpack/preset.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StateObjectRef } from '../../../../mol-state';
|
||||
import { StructureRepresentationPresetProvider, presetStaticComponent, presetSelectionComponent } from '../../../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
|
||||
export const CellpackPackingsPresetParams = {
|
||||
traceOnly: PD.Boolean(true),
|
||||
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['gaussian-surface', 'spacefill', 'point', 'orientation'])),
|
||||
hue: PD.Interval([0, 360])
|
||||
}
|
||||
export type CellpackPackingsPresetParams = PD.ValuesFor<typeof CellpackPackingsPresetParams>
|
||||
|
||||
export const CellpackPackingsPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-cellpack',
|
||||
display: { name: 'CellPack' },
|
||||
params: () => CellpackPackingsPresetParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
if (!structureCell) return {};
|
||||
|
||||
const reprProps = Object.create(null);
|
||||
const components = Object.create(null);
|
||||
|
||||
let selectionType = 'polymer'
|
||||
|
||||
if (params.traceOnly) {
|
||||
selectionType = 'trace'
|
||||
components.polymer = await presetSelectionComponent(plugin, structureCell, 'trace')
|
||||
} else {
|
||||
components.polymer = await presetStaticComponent(plugin, structureCell, 'polymer')
|
||||
}
|
||||
|
||||
if (params.representation === 'gaussian-surface') {
|
||||
Object.assign(reprProps, {
|
||||
quality: 'custom', resolution: 10, radiusOffset: 2,
|
||||
alpha: 1.0, flatShaded: false, doubleSided: false,
|
||||
ignoreHydrogens: true
|
||||
})
|
||||
} else if (params.representation === 'spacefill') {
|
||||
if (params.traceOnly) {
|
||||
Object.assign(reprProps, { sizeFactor: 2, ignoreHydrogens: true })
|
||||
} else {
|
||||
Object.assign(reprProps, { ignoreHydrogens: true })
|
||||
}
|
||||
}
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, {});
|
||||
const color = 'model-index'
|
||||
const colorParams = {
|
||||
palette: {
|
||||
name: 'generate',
|
||||
params: {
|
||||
hue: params.hue, chroma: [30, 80], luminance: [15, 85],
|
||||
clusteringStepCount: 50, minSampleCount: 800,
|
||||
maxCount: 75
|
||||
}
|
||||
}
|
||||
}
|
||||
const representations = {
|
||||
polymer: builder.buildRepresentation<any>(update, components.polymer, { type: params.representation, typeParams: { ...typeParams, ...reprProps }, color, colorParams }, { tag: selectionType })
|
||||
};
|
||||
|
||||
await plugin.updateDataState(update, { revertOnError: true });
|
||||
return { components, representations };
|
||||
}
|
||||
});
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -10,8 +10,7 @@ import { Task } from '../../../../mol-task';
|
||||
import { CellPack as _CellPack, Cell, CellPacking } from './data';
|
||||
import { createStructureFromCellPack } from './model';
|
||||
|
||||
// export const DefaultCellPackBaseUrl = 'https://cdn.jsdelivr.net/gh/mesoscope/cellPACK_data@master/cellPACK_database_1.1.0/'
|
||||
export const DefaultCellPackBaseUrl = 'https://mgldev.scripps.edu/projects/autoPACK/web/cellpackproject/'
|
||||
export const DefaultCellPackBaseUrl = 'https://mesoscope.scripps.edu/data/cellPACK_data/cellPACK_database_1.1.0/'
|
||||
|
||||
export class CellPack extends PSO.Create<_CellPack>({ name: 'CellPack', typeClass: 'Object' }) { }
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { LoadCellPackModel } from './extensions/cellpack/model';
|
||||
import { StructureFromCellpack } from './extensions/cellpack/state';
|
||||
import { DownloadStructure } from '../../mol-plugin-state/actions/structure';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
require('mol-plugin-ui/skin/light.scss')
|
||||
|
||||
function getParam(name: string, regex: string): string {
|
||||
@@ -46,6 +47,7 @@ function init() {
|
||||
},
|
||||
config: DefaultPluginSpec.config
|
||||
};
|
||||
spec.config?.set(PluginConfig.Viewport.ShowExpand, false);
|
||||
const plugin = createPlugin(document.getElementById('app')!, spec);
|
||||
trySetSnapshot(plugin);
|
||||
tryLoadFromUrl(plugin);
|
||||
|
||||
@@ -172,16 +172,17 @@
|
||||
addHeader('State');
|
||||
|
||||
var snapshot;
|
||||
addControl('Create Snapshot', () => {
|
||||
addControl('Set Snapshot', () => {
|
||||
snapshot = PluginWrapper.snapshot.get();
|
||||
// could use JSON.stringify(snapshot) and upload the data
|
||||
// console.log(JSON.stringify(snapshot, null, 2));
|
||||
});
|
||||
addControl('Apply Snapshot', () => {
|
||||
addControl('Restore Snapshot', () => {
|
||||
if (!snapshot) return;
|
||||
PluginWrapper.snapshot.set(snapshot);
|
||||
|
||||
// or download snapshot using fetch or ajax or whatever
|
||||
// or PluginWrapper.snapshot.download(url);
|
||||
});
|
||||
addControl('Download Snapshot', () => {
|
||||
snapshot = PluginWrapper.snapshot.download();
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
@@ -26,13 +26,12 @@ import { ColorNames } from '../../mol-util/color/names';
|
||||
import { InitVolumeStreaming, CreateVolumeStreamingInfo } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
|
||||
import { DefaultCanvas3DParams, Canvas3DProps } from '../../mol-canvas3d/canvas3d';
|
||||
import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
|
||||
// import { Vec3 } from 'mol-math/linear-algebra';
|
||||
// import { ParamDefinition } from 'mol-util/param-definition';
|
||||
// import { Text } from 'mol-geo/geometry/text/text';
|
||||
import { download } from '../../mol-util/download';
|
||||
import { getFormattedTime } from '../../mol-util/date';
|
||||
require('../../mol-plugin-ui/skin/light.scss')
|
||||
|
||||
class MolStarProteopediaWrapper {
|
||||
static VERSION_MAJOR = 4;
|
||||
static VERSION_MAJOR = 5;
|
||||
static VERSION_MINOR = 0;
|
||||
|
||||
private _ev = RxEventHelper.create();
|
||||
@@ -97,7 +96,6 @@ class MolStarProteopediaWrapper {
|
||||
}
|
||||
|
||||
const s = model
|
||||
.apply(StateTransforms.Model.CustomModelProperties, { autoAttach: [EvolutionaryConservation.propertyProvider.descriptor.name], properties: {} }, { ref: StateElements.ModelProps, state: { isGhost: false } })
|
||||
.apply(StateTransforms.Model.StructureFromModel, props, { ref: StateElements.Assembly });
|
||||
|
||||
s.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }, { ref: StateElements.Sequence });
|
||||
@@ -427,10 +425,16 @@ class MolStarProteopediaWrapper {
|
||||
set: (snapshot: PluginState.Snapshot) => {
|
||||
return this.plugin.state.setSnapshot(snapshot);
|
||||
},
|
||||
download: async (url: string) => {
|
||||
download: () => {
|
||||
const json = JSON.stringify(this.plugin.state.getSnapshot(), null, 2);
|
||||
const blob = new Blob([json], {type : 'application/json;charset=utf-8'});
|
||||
download(blob, `mol-star_state_${(name || getFormattedTime())}.json`)
|
||||
},
|
||||
fetch: async (url: string) => {
|
||||
try {
|
||||
const snapshot = await this.plugin.runTask(this.plugin.fetch({ url, type: 'json' }));
|
||||
await this.plugin.state.setSnapshot(snapshot);
|
||||
// TODO: is this OK to test for snapshots from server?
|
||||
await this.plugin.state.setSnapshot(snapshot?.data?.entries?.[0]?.snapshot || snapshot);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -47,8 +47,8 @@ class Camera {
|
||||
|
||||
private prevProjection = Mat4.identity();
|
||||
private prevView = Mat4.identity();
|
||||
private deltaDirection = Vec3.zero();
|
||||
private newPosition = Vec3.zero();
|
||||
private deltaDirection = Vec3();
|
||||
private newPosition = Vec3();
|
||||
|
||||
update() {
|
||||
const snapshot = this.state as Camera.Snapshot;
|
||||
@@ -290,6 +290,11 @@ function updateClip(camera: Camera) {
|
||||
far = Math.max(0, far)
|
||||
}
|
||||
|
||||
if (near === far) {
|
||||
// make sure near and far are not identical to avoid Infinity in the projection matrix
|
||||
far = near + 0.01
|
||||
}
|
||||
|
||||
camera.near = near;
|
||||
camera.far = far;
|
||||
camera.fogNear = fogNear;
|
||||
|
||||
@@ -25,23 +25,29 @@ class CameraTransitionManager {
|
||||
get target(): Readonly<Camera.Snapshot> { return this._target }
|
||||
|
||||
apply(to: Partial<Camera.Snapshot>, durationMs: number = 0, transition?: CameraTransitionManager.TransitionFunc) {
|
||||
Camera.copySnapshot(this._source, this.camera.state);
|
||||
Camera.copySnapshot(this._target, this.camera.state);
|
||||
if (!this.inTransition) {
|
||||
Camera.copySnapshot(this._source, this.camera.state);
|
||||
Camera.copySnapshot(this._target, this.camera.state);
|
||||
}
|
||||
|
||||
Camera.copySnapshot(this._target, to);
|
||||
|
||||
if (this._target.radius > this._target.radiusMax) {
|
||||
this._target.radius = this._target.radiusMax
|
||||
}
|
||||
|
||||
if (durationMs <= 0 || (typeof to.mode !== 'undefined' && to.mode !== this.camera.state.mode)) {
|
||||
if (!this.inTransition && durationMs <= 0 || (typeof to.mode !== 'undefined' && to.mode !== this.camera.state.mode)) {
|
||||
this.finish(this._target);
|
||||
return;
|
||||
}
|
||||
|
||||
this.inTransition = true;
|
||||
this.func = transition || CameraTransitionManager.defaultTransition;
|
||||
this.start = this.t;
|
||||
this.durationMs = durationMs;
|
||||
|
||||
if (!this.inTransition || durationMs > 0) {
|
||||
this.start = this.t;
|
||||
this.durationMs = durationMs;
|
||||
}
|
||||
}
|
||||
|
||||
tick(t: number) {
|
||||
|
||||
@@ -31,13 +31,16 @@ import { PixelData } from '../mol-util/image';
|
||||
import { readTexture } from '../mol-gl/compute/util';
|
||||
import { DrawPass } from './passes/draw';
|
||||
import { PickPass } from './passes/pick';
|
||||
import { Task } from '../mol-task';
|
||||
import { ImagePass, ImageProps } from './passes/image';
|
||||
import { Sphere3D } from '../mol-math/geometry';
|
||||
import { isDebugMode } from '../mol-util/debug';
|
||||
import { CameraHelper, CameraHelperParams } from './helper/camera-helper';
|
||||
|
||||
export const Canvas3DParams = {
|
||||
cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']] as const),
|
||||
camera: PD.Group({
|
||||
mode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']] as const, { label: 'Camera' }),
|
||||
helper: PD.Group(CameraHelperParams, { isFlat: true })
|
||||
}, { pivot: 'mode' }),
|
||||
cameraFog: PD.MappedStatic('on', {
|
||||
on: PD.Group({
|
||||
intensity: PD.Numeric(50, { min: 1, max: 100, step: 1 }),
|
||||
@@ -105,13 +108,12 @@ interface Canvas3D {
|
||||
}
|
||||
|
||||
const requestAnimationFrame = typeof window !== 'undefined' ? window.requestAnimationFrame : (f: (time: number) => void) => setImmediate(()=>f(Date.now()))
|
||||
const DefaultRunTask = (task: Task<unknown>) => task.run()
|
||||
|
||||
namespace Canvas3D {
|
||||
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 function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}, runTask = DefaultRunTask) {
|
||||
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}) {
|
||||
const gl = getGLContext(canvas, {
|
||||
alpha: true,
|
||||
antialias: true,
|
||||
@@ -156,10 +158,10 @@ namespace Canvas3D {
|
||||
if (isDebugMode) console.log('context restored')
|
||||
}, false)
|
||||
|
||||
return Canvas3D.create(webgl, input, props, runTask)
|
||||
return Canvas3D.create(webgl, input, props)
|
||||
}
|
||||
|
||||
export function create(webgl: WebGLContext, input: InputObserver, props: Partial<Canvas3DProps> = {}, runTask = DefaultRunTask): Canvas3D {
|
||||
export function create(webgl: WebGLContext, input: InputObserver, props: Partial<Canvas3DProps> = {}): Canvas3D {
|
||||
const p = { ...DefaultCanvas3DParams, ...props }
|
||||
|
||||
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>()
|
||||
@@ -178,7 +180,7 @@ namespace Canvas3D {
|
||||
|
||||
const camera = new Camera({
|
||||
position: Vec3.create(0, 0, 100),
|
||||
mode: p.cameraMode,
|
||||
mode: p.camera.mode,
|
||||
fog: p.cameraFog.name === 'on' ? p.cameraFog.params.intensity : 0,
|
||||
clipFar: p.cameraClipping.far
|
||||
})
|
||||
@@ -187,8 +189,9 @@ namespace Canvas3D {
|
||||
const renderer = Renderer.create(webgl, p.renderer)
|
||||
const debugHelper = new BoundingSphereHelper(webgl, scene, p.debug);
|
||||
const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input);
|
||||
const cameraHelper = new CameraHelper(webgl, p.camera.helper);
|
||||
|
||||
const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper)
|
||||
const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper, cameraHelper)
|
||||
const pickPass = new PickPass(webgl, renderer, scene, camera, 0.5)
|
||||
const postprocessing = new PostprocessingPass(webgl, camera, drawPass, p.postprocessing)
|
||||
const multiSample = new MultiSamplePass(webgl, camera, drawPass, postprocessing, p.multiSample)
|
||||
@@ -300,6 +303,7 @@ namespace Canvas3D {
|
||||
|
||||
function resolveCameraReset() {
|
||||
if (!cameraResetRequested) return;
|
||||
|
||||
const { center, radius } = scene.boundingSphereVisible;
|
||||
if (radius > 0) {
|
||||
const duration = nextCameraResetDuration === undefined ? p.cameraResetDurationMs : nextCameraResetDuration
|
||||
@@ -319,6 +323,8 @@ namespace Canvas3D {
|
||||
function shouldResetCamera() {
|
||||
if (camera.state.radiusMax === 0) return true;
|
||||
|
||||
if (camera.transition.inTransition || nextCameraResetSnapshot) return false;
|
||||
|
||||
let cameraSphereOverlapsNone = true
|
||||
Sphere3D.set(cameraSphere, camera.state.target, camera.state.radius)
|
||||
|
||||
@@ -347,10 +353,12 @@ namespace Canvas3D {
|
||||
if (!scene.commit(isSynchronous ? void 0 : sceneCommitTimeoutMs)) return false;
|
||||
|
||||
if (debugHelper.isEnabled) debugHelper.update();
|
||||
if (reprCount.value === 0 || shouldResetCamera()) cameraResetRequested = true;
|
||||
if (reprCount.value === 0 || shouldResetCamera()) {
|
||||
cameraResetRequested = true;
|
||||
}
|
||||
if (oldBoundingSphereVisible.radius === 0) nextCameraResetDuration = 0;
|
||||
|
||||
camera.setState({ radiusMax: scene.boundingSphere.radius })
|
||||
camera.setState({ radiusMax: scene.boundingSphere.radius }, 0)
|
||||
reprCount.next(reprRenderObjects.size);
|
||||
|
||||
return true;
|
||||
@@ -470,8 +478,8 @@ namespace Canvas3D {
|
||||
reprCount,
|
||||
setProps: (props: Partial<Canvas3DProps>) => {
|
||||
const cameraState: Partial<Camera.Snapshot> = Object.create(null)
|
||||
if (props.cameraMode !== undefined && props.cameraMode !== camera.state.mode) {
|
||||
cameraState.mode = props.cameraMode
|
||||
if (props.camera && props.camera.mode !== undefined && props.camera.mode !== camera.state.mode) {
|
||||
cameraState.mode = props.camera.mode
|
||||
}
|
||||
if (props.cameraFog !== undefined) {
|
||||
const newFog = props.cameraFog.name === 'on' ? props.cameraFog.params.intensity : 0
|
||||
@@ -491,6 +499,7 @@ namespace Canvas3D {
|
||||
}
|
||||
if (Object.keys(cameraState).length > 0) camera.setState(cameraState)
|
||||
|
||||
if (props.camera?.helper) cameraHelper.setProps(props.camera.helper)
|
||||
if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs
|
||||
if (props.transparentBackground !== undefined) p.transparentBackground = props.transparentBackground
|
||||
|
||||
@@ -503,7 +512,7 @@ namespace Canvas3D {
|
||||
requestDraw(true)
|
||||
},
|
||||
getImagePass: (props: Partial<ImageProps> = {}) => {
|
||||
return new ImagePass(webgl, renderer, scene, camera, debugHelper, props)
|
||||
return new ImagePass(webgl, renderer, scene, camera, debugHelper, cameraHelper, props)
|
||||
},
|
||||
|
||||
get props() {
|
||||
@@ -512,7 +521,10 @@ namespace Canvas3D {
|
||||
: 0
|
||||
|
||||
return {
|
||||
cameraMode: camera.state.mode,
|
||||
camera: {
|
||||
mode: camera.state.mode,
|
||||
helper: { ...cameraHelper.props }
|
||||
},
|
||||
cameraFog: camera.state.fog > 0
|
||||
? { name: 'on' as const, params: { intensity: camera.state.fog } }
|
||||
: { name: 'off' as const, params: {} },
|
||||
|
||||
@@ -208,7 +208,7 @@ namespace TrackballControls {
|
||||
function focusCamera() {
|
||||
const factor = (_focusEnd[1] - _focusStart[1]) * p.zoomSpeed
|
||||
if (factor !== 0.0) {
|
||||
const radius = Math.max(1, camera.state.radius + 10 * factor)
|
||||
const radius = Math.max(1, camera.state.radius + camera.state.radius * factor)
|
||||
camera.setState({ radius })
|
||||
}
|
||||
|
||||
@@ -248,10 +248,14 @@ namespace TrackballControls {
|
||||
}
|
||||
}
|
||||
|
||||
/** Ensure the distance between object and target is within the min/max distance */
|
||||
/**
|
||||
* Ensure the distance between object and target is within the min/max distance
|
||||
* and not too large compared to `camera.state.radiusMax`
|
||||
*/
|
||||
function checkDistances() {
|
||||
if (Vec3.squaredMagnitude(_eye) > p.maxDistance * p.maxDistance) {
|
||||
Vec3.setMagnitude(_eye, _eye, p.maxDistance)
|
||||
const maxDistance = Math.min(Math.max(camera.state.radiusMax * 1000, 0.01), p.maxDistance)
|
||||
if (Vec3.squaredMagnitude(_eye) > maxDistance * maxDistance) {
|
||||
Vec3.setMagnitude(_eye, _eye, maxDistance)
|
||||
Vec3.add(camera.position, camera.target, _eye)
|
||||
Vec2.copy(_zoomStart, _zoomEnd)
|
||||
Vec2.copy(_focusStart, _focusEnd)
|
||||
|
||||
@@ -20,9 +20,10 @@ import { ValueCell } from '../../mol-util';
|
||||
import { Geometry } from '../../mol-geo/geometry/geometry';
|
||||
|
||||
export const DebugHelperParams = {
|
||||
sceneBoundingSpheres: PD.Boolean(false, { description: 'Show scene bounding spheres.' }),
|
||||
objectBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of render objects.' }),
|
||||
instanceBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of instances.' }),
|
||||
sceneBoundingSpheres: PD.Boolean(false, { description: 'Show full scene bounding spheres.' }),
|
||||
visibleSceneBoundingSpheres: PD.Boolean(false, { description: 'Show visible scene bounding spheres.' }),
|
||||
objectBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of visible render objects.' }),
|
||||
instanceBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of visible instances.' }),
|
||||
}
|
||||
export type DebugHelperParams = typeof DebugHelperParams
|
||||
export type DebugHelperProps = PD.Values<DebugHelperParams>
|
||||
@@ -37,6 +38,7 @@ export class BoundingSphereHelper {
|
||||
private objectsData = new Map<GraphicsRenderObject, BoundingSphereData>()
|
||||
private instancesData = new Map<GraphicsRenderObject, BoundingSphereData>()
|
||||
private sceneData: BoundingSphereData | undefined
|
||||
private visibleSceneData: BoundingSphereData | undefined
|
||||
|
||||
constructor(ctx: WebGLContext, parent: Scene, props: Partial<DebugHelperProps>) {
|
||||
this.scene = Scene.create(ctx)
|
||||
@@ -45,9 +47,12 @@ export class BoundingSphereHelper {
|
||||
}
|
||||
|
||||
update() {
|
||||
const newSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphere, this.sceneData, ColorNames.grey, sceneMaterialId)
|
||||
const newSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphere, this.sceneData, ColorNames.lightgrey, sceneMaterialId)
|
||||
if (newSceneData) this.sceneData = newSceneData
|
||||
|
||||
const newVisibleSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphereVisible, this.visibleSceneData, ColorNames.black, visibleSceneMaterialId)
|
||||
if (newVisibleSceneData) this.visibleSceneData = newVisibleSceneData
|
||||
|
||||
this.parent.forEach((r, ro) => {
|
||||
const objectData = this.objectsData.get(ro)
|
||||
const newObjectData = updateBoundingSphereData(this.scene, r.values.boundingSphere.ref.value, objectData, ColorNames.tomato, objectMaterialId)
|
||||
@@ -88,6 +93,10 @@ export class BoundingSphereHelper {
|
||||
this.sceneData.renderObject.state.visible = this._props.sceneBoundingSpheres
|
||||
}
|
||||
|
||||
if (this.visibleSceneData) {
|
||||
this.visibleSceneData.renderObject.state.visible = this._props.visibleSceneBoundingSpheres
|
||||
}
|
||||
|
||||
this.parent.forEach((_, ro) => {
|
||||
const objectData = this.objectsData.get(ro)
|
||||
if (objectData) objectData.renderObject.state.visible = ro.state.visible && this._props.objectBoundingSpheres
|
||||
@@ -104,7 +113,10 @@ export class BoundingSphereHelper {
|
||||
}
|
||||
|
||||
get isEnabled() {
|
||||
return this._props.sceneBoundingSpheres || this._props.objectBoundingSpheres || this._props.instanceBoundingSpheres
|
||||
return (
|
||||
this._props.sceneBoundingSpheres || this._props.visibleSceneBoundingSpheres ||
|
||||
this._props.objectBoundingSpheres || this._props.instanceBoundingSpheres
|
||||
)
|
||||
}
|
||||
get props() { return this._props as Readonly<DebugHelperProps> }
|
||||
|
||||
@@ -141,6 +153,7 @@ function createBoundingSphereMesh(boundingSphere: Sphere3D, mesh?: Mesh) {
|
||||
}
|
||||
|
||||
const sceneMaterialId = getNextMaterialId()
|
||||
const visibleSceneMaterialId = getNextMaterialId()
|
||||
const objectMaterialId = getNextMaterialId()
|
||||
const instanceMaterialId = getNextMaterialId()
|
||||
|
||||
|
||||
181
src/mol-canvas3d/helper/camera-helper.ts
Normal file
181
src/mol-canvas3d/helper/camera-helper.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import Scene from '../../mol-gl/scene';
|
||||
import { Camera } from '../camera';
|
||||
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
|
||||
import { GraphicsRenderObject } from '../../mol-gl/render-object';
|
||||
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
|
||||
import { Viewport } from '../camera/util';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
import { Sphere3D } from '../../mol-math/geometry';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import produce from 'immer';
|
||||
import { Shape } from '../../mol-model/shape';
|
||||
import { ShapeRepresentation } from '../../mol-repr/shape/representation';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
|
||||
// TODO add scale line/grid
|
||||
|
||||
const AxesParams = {
|
||||
...Mesh.Params,
|
||||
alpha: { ...Mesh.Params.alpha, defaultValue: 0.33 },
|
||||
ignoreLight: { ...Mesh.Params.ignoreLight, defaultValue: true },
|
||||
colorX: PD.Color(ColorNames.red, { isEssential: true }),
|
||||
colorY: PD.Color(ColorNames.green, { isEssential: true }),
|
||||
colorZ: PD.Color(ColorNames.blue, { isEssential: true }),
|
||||
scale: PD.Numeric(0.33, { min: 0.1, max: 2, step: 0.1 }, { isEssential: true }),
|
||||
}
|
||||
type AxesParams = typeof AxesParams
|
||||
type AxesProps = PD.Values<AxesParams>
|
||||
|
||||
export const CameraHelperParams = {
|
||||
axes: PD.MappedStatic('on', {
|
||||
on: PD.Group(AxesParams),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Show camera orientation axes' }),
|
||||
}
|
||||
export type CameraHelperParams = typeof CameraHelperParams
|
||||
export type CameraHelperProps = PD.Values<CameraHelperParams>
|
||||
|
||||
export class CameraHelper {
|
||||
scene: Scene
|
||||
camera: Camera
|
||||
props: CameraHelperProps = {
|
||||
axes: { name: 'off', params: {} }
|
||||
}
|
||||
|
||||
private renderObject: GraphicsRenderObject | undefined
|
||||
|
||||
constructor(private webgl: WebGLContext, props: Partial<CameraHelperProps> = {}) {
|
||||
this.scene = Scene.create(webgl)
|
||||
|
||||
this.camera = new Camera()
|
||||
Vec3.set(this.camera.up, 0, 1, 0)
|
||||
Vec3.set(this.camera.target, 0, 0, 0)
|
||||
|
||||
this.setProps(props)
|
||||
}
|
||||
|
||||
setProps(props: Partial<CameraHelperProps>) {
|
||||
this.props = produce(this.props, p => {
|
||||
if (props.axes !== undefined) {
|
||||
p.axes.name = props.axes.name
|
||||
if (props.axes.name === 'on') {
|
||||
this.scene.clear()
|
||||
const params = { ...props.axes.params, scale: props.axes.params.scale * this.webgl.pixelRatio }
|
||||
this.renderObject = undefined
|
||||
createAxesRenderObject(params).then(renderObject => {
|
||||
this.renderObject = renderObject
|
||||
this.scene.add(this.renderObject)
|
||||
this.scene.commit()
|
||||
})
|
||||
|
||||
Vec3.set(this.camera.position, 0, 0, params.scale * 200)
|
||||
Mat4.lookAt(this.camera.view, this.camera.position, this.camera.target, this.camera.up)
|
||||
|
||||
p.axes.params = { ...props.axes.params }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
get isEnabled() {
|
||||
return this.props.axes.name === 'on'
|
||||
}
|
||||
|
||||
update(camera: Camera) {
|
||||
if (!this.renderObject) return
|
||||
|
||||
updateCamera(this.camera, camera.viewport)
|
||||
|
||||
const m = this.renderObject.values.aTransform.ref.value as unknown as Mat4
|
||||
Mat4.extractRotation(m, camera.view)
|
||||
|
||||
const r = this.renderObject.values.boundingSphere.ref.value.radius
|
||||
Mat4.setTranslation(m, Vec3.create(
|
||||
-camera.viewport.width / 2 + r,
|
||||
-camera.viewport.height / 2 + r,
|
||||
0
|
||||
))
|
||||
|
||||
ValueCell.update(this.renderObject.values.aTransform, this.renderObject.values.aTransform.ref.value)
|
||||
this.scene.update([this.renderObject], true)
|
||||
}
|
||||
}
|
||||
|
||||
function updateCamera(camera: Camera, viewport: Viewport) {
|
||||
const { near, far } = camera
|
||||
|
||||
const fullLeft = -(viewport.width - viewport.x) / 2
|
||||
const fullRight = (viewport.width - viewport.x) / 2
|
||||
const fullTop = (viewport.height - viewport.y) / 2
|
||||
const fullBottom = -(viewport.height - viewport.y) / 2
|
||||
|
||||
const dx = (fullRight - fullLeft) / 2
|
||||
const dy = (fullTop - fullBottom) / 2
|
||||
const cx = (fullRight + fullLeft) / 2
|
||||
const cy = (fullTop + fullBottom) / 2
|
||||
|
||||
const left = cx - dx
|
||||
const right = cx + dx
|
||||
const top = cy + dy
|
||||
const bottom = cy - dy
|
||||
|
||||
Mat4.ortho(camera.projection, left, right, top, bottom, near, far)
|
||||
}
|
||||
|
||||
function createAxesMesh(scale: number, mesh?: Mesh) {
|
||||
const state = MeshBuilder.createState(512, 256, mesh)
|
||||
const radius = 0.05 * scale
|
||||
const x = Vec3.scale(Vec3(), Vec3.unitX, scale)
|
||||
const y = Vec3.scale(Vec3(), Vec3.unitY, scale)
|
||||
const z = Vec3.scale(Vec3(), Vec3.unitZ, scale)
|
||||
const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments: 32 }
|
||||
|
||||
state.currentGroup = 0
|
||||
addSphere(state, Vec3.origin, radius, 2)
|
||||
|
||||
state.currentGroup = 1
|
||||
addSphere(state, x, radius, 2)
|
||||
addCylinder(state, Vec3.origin, x, 1, cylinderProps)
|
||||
|
||||
state.currentGroup = 2
|
||||
addSphere(state, y, radius, 2)
|
||||
addCylinder(state, Vec3.origin, y, 1, cylinderProps)
|
||||
|
||||
state.currentGroup = 3
|
||||
addSphere(state, z, radius, 2)
|
||||
addCylinder(state, Vec3.origin, z, 1, cylinderProps)
|
||||
|
||||
return MeshBuilder.getMesh(state)
|
||||
}
|
||||
|
||||
function getAxesShape(ctx: RuntimeContext, data: {}, props: AxesProps, shape?: Shape<Mesh>) {
|
||||
const scale = 100 * props.scale
|
||||
const mesh = createAxesMesh(scale, shape?.geometry)
|
||||
mesh.setBoundingSphere(Sphere3D.create(Vec3.create(scale / 2, scale / 2, scale / 2), scale + scale / 4))
|
||||
const getColor = (groupId: number) => {
|
||||
switch (groupId) {
|
||||
case 1: return props.colorX
|
||||
case 2: return props.colorY
|
||||
case 3: return props.colorZ
|
||||
default: return ColorNames.grey
|
||||
}
|
||||
}
|
||||
return Shape.create('axes', {}, mesh, getColor, () => 1, () => '')
|
||||
}
|
||||
|
||||
async function createAxesRenderObject(props: AxesProps) {
|
||||
const repr = ShapeRepresentation(getAxesShape, Mesh.Utils)
|
||||
await repr.createOrUpdate(props, {}).run()
|
||||
return repr.renderObjects[0]
|
||||
}
|
||||
@@ -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 Scene from '../../mol-gl/scene';
|
||||
import { BoundingSphereHelper } from '../helper/bounding-sphere-helper';
|
||||
import { Texture } from '../../mol-gl/webgl/texture';
|
||||
import { Camera } from '../camera';
|
||||
import { CameraHelper } from '../helper/camera-helper';
|
||||
|
||||
export class DrawPass {
|
||||
colorTarget: RenderTarget
|
||||
@@ -19,7 +20,7 @@ export class DrawPass {
|
||||
|
||||
private depthTarget: RenderTarget | null
|
||||
|
||||
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper) {
|
||||
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper, private cameraHelper: CameraHelper) {
|
||||
const { gl, extensions, resources } = webgl
|
||||
const width = gl.drawingBufferWidth
|
||||
const height = gl.drawingBufferHeight
|
||||
@@ -43,7 +44,7 @@ export class DrawPass {
|
||||
}
|
||||
|
||||
render(toDrawingBuffer: boolean, transparentBackground: boolean) {
|
||||
const { webgl, renderer, scene, camera, debugHelper, colorTarget, depthTarget } = this
|
||||
const { webgl, renderer, colorTarget, depthTarget } = this
|
||||
if (toDrawingBuffer) {
|
||||
webgl.unbindFramebuffer()
|
||||
} else {
|
||||
@@ -55,21 +56,26 @@ export class DrawPass {
|
||||
}
|
||||
|
||||
renderer.setViewport(0, 0, colorTarget.getWidth(), colorTarget.getHeight())
|
||||
renderer.render(scene, camera, 'color', true, transparentBackground)
|
||||
if (debugHelper.isEnabled) {
|
||||
debugHelper.syncVisibility()
|
||||
renderer.render(debugHelper.scene, camera, 'color', false, transparentBackground)
|
||||
}
|
||||
this.renderInternal('color', transparentBackground)
|
||||
|
||||
// do a depth pass if not rendering to drawing buffer and
|
||||
// extensions.depthTexture is unsupported (i.e. depthTarget is set)
|
||||
if (!toDrawingBuffer && depthTarget) {
|
||||
depthTarget.bind()
|
||||
renderer.render(scene, camera, 'depth', true, transparentBackground)
|
||||
if (debugHelper.isEnabled) {
|
||||
debugHelper.syncVisibility()
|
||||
renderer.render(debugHelper.scene, camera, 'depth', false, transparentBackground)
|
||||
}
|
||||
this.renderInternal('depth', transparentBackground)
|
||||
}
|
||||
}
|
||||
|
||||
private renderInternal(variant: 'color' | 'depth', transparentBackground: boolean) {
|
||||
const { renderer, scene, camera, debugHelper, cameraHelper } = this
|
||||
renderer.render(scene, camera, variant, true, transparentBackground)
|
||||
if (debugHelper.isEnabled) {
|
||||
debugHelper.syncVisibility()
|
||||
renderer.render(debugHelper.scene, camera, variant, false, transparentBackground)
|
||||
}
|
||||
if (cameraHelper.isEnabled) {
|
||||
cameraHelper.update(camera)
|
||||
renderer.render(cameraHelper.scene, cameraHelper.camera, variant, false, transparentBackground)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import { PostprocessingPass, PostprocessingParams } from './postprocessing'
|
||||
import { MultiSamplePass, MultiSampleParams } from './multi-sample'
|
||||
import { Camera } from '../camera';
|
||||
import { Viewport } from '../camera/util';
|
||||
import { CameraHelper } from '../helper/camera-helper';
|
||||
|
||||
export const ImageParams = {
|
||||
transparentBackground: PD.Boolean(false),
|
||||
@@ -39,12 +40,12 @@ export class ImagePass {
|
||||
get width() { return this._width }
|
||||
get height() { return this._height }
|
||||
|
||||
constructor(webgl: WebGLContext, private renderer: Renderer, scene: Scene, private camera: Camera, debugHelper: BoundingSphereHelper, props: Partial<ImageProps>) {
|
||||
constructor(webgl: WebGLContext, private renderer: Renderer, scene: Scene, private camera: Camera, debugHelper: BoundingSphereHelper, cameraHelper: CameraHelper, props: Partial<ImageProps>) {
|
||||
const p = { ...PD.getDefaultValues(ImageParams), ...props }
|
||||
|
||||
this._transparentBackground = p.transparentBackground
|
||||
|
||||
this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper)
|
||||
this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper, cameraHelper)
|
||||
this.postprocessing = new PostprocessingPass(webgl, this._camera, this.drawPass, p.postprocessing)
|
||||
this.multiSample = new MultiSamplePass(webgl, this._camera, this.drawPass, this.postprocessing, p.multiSample)
|
||||
|
||||
|
||||
@@ -4,29 +4,28 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ValueCell } from '../../../mol-util'
|
||||
import { Sphere3D, Box3D } from '../../../mol-math/geometry'
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { DirectVolumeValues } from '../../../mol-gl/renderable/direct-volume';
|
||||
import { Vec3, Mat4, Vec2 } from '../../../mol-math/linear-algebra';
|
||||
import { Box } from '../../primitive/box';
|
||||
import { createTransferFunctionTexture, getControlPointsFromVec2Array } from './transfer-function';
|
||||
import { Texture } from '../../../mol-gl/webgl/texture';
|
||||
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
|
||||
import { TransformData } from '../transform-data';
|
||||
import { createColors } from '../color-data';
|
||||
import { createMarkers } from '../marker-data';
|
||||
import { GeometryUtils } from '../geometry';
|
||||
import { transformPositionArray } from '../../../mol-geo/util';
|
||||
import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
import { RenderableState } from '../../../mol-gl/renderable';
|
||||
import { ColorListOptions, ColorListName } from '../../../mol-util/color/lists';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { BaseGeometry } from '../base';
|
||||
import { createEmptyOverpaint } from '../overpaint-data';
|
||||
import { createEmptyTransparency } from '../transparency-data';
|
||||
import { hashFnv32a } from '../../../mol-data/util';
|
||||
import { transformPositionArray } from '../../../mol-geo/util';
|
||||
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
|
||||
import { RenderableState } from '../../../mol-gl/renderable';
|
||||
import { DirectVolumeValues } from '../../../mol-gl/renderable/direct-volume';
|
||||
import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
|
||||
import { Texture } from '../../../mol-gl/webgl/texture';
|
||||
import { Box3D, Sphere3D } from '../../../mol-math/geometry';
|
||||
import { Mat4, Vec2, Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
import { ValueCell } from '../../../mol-util';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { Box } from '../../primitive/box';
|
||||
import { BaseGeometry } from '../base';
|
||||
import { createColors } from '../color-data';
|
||||
import { GeometryUtils } from '../geometry';
|
||||
import { createMarkers } from '../marker-data';
|
||||
import { createEmptyOverpaint } from '../overpaint-data';
|
||||
import { TransformData } from '../transform-data';
|
||||
import { createEmptyTransparency } from '../transparency-data';
|
||||
import { createTransferFunctionTexture, getControlPointsFromVec2Array } from './transfer-function';
|
||||
|
||||
const VolumeBox = Box()
|
||||
const RenderModeOptions = [['isosurface', 'Isosurface'], ['volume', 'Volume']] as [string, string][]
|
||||
@@ -117,7 +116,7 @@ export namespace DirectVolume {
|
||||
Vec2.create(0.19, 0.0), Vec2.create(0.2, 0.05), Vec2.create(0.25, 0.05), Vec2.create(0.26, 0.0),
|
||||
Vec2.create(0.79, 0.0), Vec2.create(0.8, 0.05), Vec2.create(0.85, 0.05), Vec2.create(0.86, 0.0),
|
||||
]),
|
||||
list: PD.ColorList<ColorListName>('red-yellow-blue', ColorListOptions),
|
||||
list: PD.ColorList('red-yellow-blue'),
|
||||
}
|
||||
export type Params = typeof Params
|
||||
|
||||
@@ -148,7 +147,7 @@ export namespace DirectVolume {
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount)
|
||||
|
||||
const controlPoints = getControlPointsFromVec2Array(props.controlPoints)
|
||||
const transferTex = createTransferFunctionTexture(controlPoints, props.list)
|
||||
const transferTex = createTransferFunctionTexture(controlPoints, props.list.colors)
|
||||
|
||||
const maxSteps = Math.ceil(Vec3.magnitude(gridDimension.ref.value)) * 2 * 5
|
||||
|
||||
@@ -193,7 +192,7 @@ export namespace DirectVolume {
|
||||
ValueCell.updateIfChanged(values.dRenderMode, props.renderMode)
|
||||
|
||||
const controlPoints = getControlPointsFromVec2Array(props.controlPoints)
|
||||
createTransferFunctionTexture(controlPoints, props.list, values.tTransferTex)
|
||||
createTransferFunctionTexture(controlPoints, props.list.colors, values.tTransferTex)
|
||||
}
|
||||
|
||||
function updateBoundingSphere(values: DirectVolumeValues, directVolume: DirectVolume) {
|
||||
|
||||
@@ -29,7 +29,7 @@ export interface TextureVolume<T extends Uint8Array | Float32Array> {
|
||||
readonly depth: number
|
||||
}
|
||||
|
||||
export function createTextureImage<T extends Uint8Array | Float32Array>(n: number, itemSize: number, arrayCtor: new (length: number) => T, array?: T): TextureImage<T> {
|
||||
export function createTextureImage<T extends Uint8Array | Float32Array>(n: number, itemSize: number, arrayCtor: new (length: number)=> T, array?: T): TextureImage<T> {
|
||||
const { length, width, height } = calculateTextureInfo(n, itemSize)
|
||||
array = array && array.length >= length ? array : new arrayCtor(length)
|
||||
return { array, width, height }
|
||||
@@ -95,12 +95,12 @@ export function calculateInvariantBoundingSphere(position: Float32Array, positio
|
||||
boundaryHelper.reset()
|
||||
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
|
||||
Vec3.fromArray(v, position, i)
|
||||
boundaryHelper.includeStep(v)
|
||||
boundaryHelper.includePosition(v)
|
||||
}
|
||||
boundaryHelper.finishedIncludeStep()
|
||||
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
|
||||
Vec3.fromArray(v, position, i)
|
||||
boundaryHelper.radiusStep(v)
|
||||
boundaryHelper.radiusPosition(v)
|
||||
}
|
||||
|
||||
const sphere = boundaryHelper.getSphere()
|
||||
@@ -126,25 +126,25 @@ export function calculateTransformBoundingSphere(invariantBoundingSphere: Sphere
|
||||
for (let i = 0, _i = transformCount; i < _i; ++i) {
|
||||
for (const e of extrema) {
|
||||
Vec3.transformMat4Offset(v, e, transform, 0, 0, i * 16)
|
||||
boundaryHelper.includeStep(v)
|
||||
boundaryHelper.includePosition(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)
|
||||
boundaryHelper.radiusPosition(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.includePositionRadius(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)
|
||||
boundaryHelper.radiusPositionRadius(v, radius)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,13 +28,7 @@ function calculateBoundingSphere(renderables: Renderable<RenderableValues & Base
|
||||
const boundingSphere = renderables[i].values.boundingSphere.ref.value
|
||||
if (!boundingSphere.radius) continue;
|
||||
|
||||
if (Sphere3D.hasExtrema(boundingSphere)) {
|
||||
for (const e of boundingSphere.extrema) {
|
||||
boundaryHelper.includeStep(e)
|
||||
}
|
||||
} else {
|
||||
boundaryHelper.includeSphereStep(boundingSphere.center, boundingSphere.radius);
|
||||
}
|
||||
boundaryHelper.includeSphere(boundingSphere);
|
||||
}
|
||||
boundaryHelper.finishedIncludeStep();
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
@@ -43,13 +37,7 @@ function calculateBoundingSphere(renderables: Renderable<RenderableValues & Base
|
||||
const boundingSphere = renderables[i].values.boundingSphere.ref.value
|
||||
if (!boundingSphere.radius) continue;
|
||||
|
||||
if (Sphere3D.hasExtrema(boundingSphere)) {
|
||||
for (const e of boundingSphere.extrema) {
|
||||
boundaryHelper.radiusStep(e)
|
||||
}
|
||||
} else {
|
||||
boundaryHelper.radiusSphereStep(boundingSphere.center, boundingSphere.radius);
|
||||
}
|
||||
boundaryHelper.radiusSphere(boundingSphere);
|
||||
}
|
||||
|
||||
return boundaryHelper.getSphere(boundingSphere);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.323, IHM 1.08, CARB draft.
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.324, IHM 1.09, CARB draft.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
@@ -129,7 +129,7 @@ export const BIRD_Schema = {
|
||||
/**
|
||||
* Defines the polymer characteristic of the entity.
|
||||
*/
|
||||
type: str,
|
||||
type: Aliased<'polymer' | 'polymer-like' | 'non-polymer' | 'branched'>(str),
|
||||
/**
|
||||
* Additional details about this entity.
|
||||
*/
|
||||
@@ -423,7 +423,7 @@ export const BIRD_Schema = {
|
||||
/**
|
||||
* The monomer type for the sequence.
|
||||
*/
|
||||
type: str,
|
||||
type: Aliased<'peptide-like' | 'saccharide'>(str),
|
||||
/**
|
||||
* A flag to indicate a non-ribosomal entity.
|
||||
*/
|
||||
@@ -487,7 +487,7 @@ export const BIRD_Schema = {
|
||||
/**
|
||||
* An identifier for the wwPDB site creating or modifying the molecule.
|
||||
*/
|
||||
processing_site: Aliased<'RCSB' | 'PDBe' | 'PDBJ' | 'BMRB'>(str),
|
||||
processing_site: Aliased<'RCSB' | 'PDBe' | 'PDBJ' | 'BMRB' | 'PDBC'>(str),
|
||||
/**
|
||||
* The action associated with this audit record.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.323, IHM 1.08, CARB draft.
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.324, IHM 1.09, CARB draft.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
@@ -175,7 +175,7 @@ export const CCD_Schema = {
|
||||
* This data item identifies the deposition site that processed
|
||||
* this chemical component defintion.
|
||||
*/
|
||||
pdbx_processing_site: Aliased<'PDBE' | 'EBI' | 'PDBJ' | 'RCSB'>(str),
|
||||
pdbx_processing_site: Aliased<'PDBE' | 'EBI' | 'PDBJ' | 'PDBC' | 'RCSB'>(str),
|
||||
},
|
||||
/**
|
||||
* Data items in the CHEM_COMP_ATOM category record details about
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'CifCore' schema file. Dictionary versions: CifCore 3.0.11.
|
||||
* Code-generated 'CifCore' schema file. Dictionary versions: CifCore 3.0.13.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.323, IHM 1.08, CARB draft.
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.324, IHM 1.09, CARB draft.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
@@ -185,7 +185,7 @@ export const mmCIF_Schema = {
|
||||
/**
|
||||
* The fraction of the atom type present at this site.
|
||||
* The sum of the occupancies of all the atom types at this site
|
||||
* may not significantly exceed 1.0 unless it is a dummy site.
|
||||
* may not exceed 1.0 unless it is a dummy site.
|
||||
*/
|
||||
occupancy: float,
|
||||
/**
|
||||
@@ -1774,11 +1774,11 @@ export const mmCIF_Schema = {
|
||||
/**
|
||||
* The site where the file was deposited.
|
||||
*/
|
||||
deposit_site: Aliased<'NDB' | 'RCSB' | 'PDBE' | 'PDBJ' | 'BMRB' | 'BNL'>(str),
|
||||
deposit_site: Aliased<'NDB' | 'RCSB' | 'PDBE' | 'PDBJ' | 'BMRB' | 'BNL' | 'PDBC'>(str),
|
||||
/**
|
||||
* The site where the file was deposited.
|
||||
*/
|
||||
process_site: Aliased<'NDB' | 'RCSB' | 'PDBE' | 'PDBJ' | 'BNL'>(str),
|
||||
process_site: Aliased<'NDB' | 'RCSB' | 'PDBE' | 'PDBJ' | 'BNL' | 'PDBC'>(str),
|
||||
/**
|
||||
* Code for status of chemical shift data file.
|
||||
*/
|
||||
@@ -2192,7 +2192,7 @@ export const mmCIF_Schema = {
|
||||
/**
|
||||
* Defines the polymer characteristic of the entity.
|
||||
*/
|
||||
type: str,
|
||||
type: Aliased<'polymer' | 'polymer-like' | 'non-polymer' | 'branched'>(str),
|
||||
/**
|
||||
* Additional details about this entity.
|
||||
*/
|
||||
@@ -2575,6 +2575,48 @@ export const mmCIF_Schema = {
|
||||
*/
|
||||
ordinal: int,
|
||||
},
|
||||
/**
|
||||
* Data items in the pdbx_entity_instance_feature category records
|
||||
* special features of selected entity instances.
|
||||
*/
|
||||
pdbx_entity_instance_feature: {
|
||||
/**
|
||||
* Special structural details about this entity instance.
|
||||
*/
|
||||
details: str,
|
||||
/**
|
||||
* A feature type associated with entity instance.
|
||||
*/
|
||||
feature_type: Aliased<'SUBJECT OF INVESTIGATION' | 'NO FUNCTIONAL ROLE' | 'OTHER'>(str),
|
||||
/**
|
||||
* Author instance identifier (formerly PDB Chain ID)
|
||||
*/
|
||||
auth_asym_id: str,
|
||||
/**
|
||||
* Instance identifier for this entity.
|
||||
*/
|
||||
asym_id: str,
|
||||
/**
|
||||
* Author provided residue number.
|
||||
*/
|
||||
auth_seq_num: str,
|
||||
/**
|
||||
* Position in the sequence.
|
||||
*/
|
||||
seq_num: int,
|
||||
/**
|
||||
* Chemical component identifier
|
||||
*/
|
||||
comp_id: str,
|
||||
/**
|
||||
* The author provided chemical component identifier
|
||||
*/
|
||||
auth_comp_id: str,
|
||||
/**
|
||||
* An ordinal index for this category
|
||||
*/
|
||||
ordinal: int,
|
||||
},
|
||||
/**
|
||||
* Data items in the IHM_STARTING_MODEL_DETAILS category records the
|
||||
* details about structural models used as starting inputs in
|
||||
@@ -4585,6 +4627,21 @@ export const mmCIF_Schema = {
|
||||
* PDBX_ENTITY_BRANCH_LIST category.
|
||||
*/
|
||||
num: int,
|
||||
/**
|
||||
* This data item is a pointer to _atom_site.auth_asym_id in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
pdb_asym_id: str,
|
||||
/**
|
||||
* This data item is a pointer to _atom_site.auth_seq_id in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
pdb_seq_num: str,
|
||||
/**
|
||||
* This data item is a pointer to _atom_site.auth_comp_id in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
pdb_mon_id: str,
|
||||
/**
|
||||
* This data item is a pointer to _atom_site.pdbx_auth_asym_id in the
|
||||
* ATOM_SITE category.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import TextEncoder from './cif/encoder/text'
|
||||
import BinaryEncoder, { EncodingProvider } from './cif/encoder/binary'
|
||||
import BinaryEncoder, { BinaryEncodingProvider } from './cif/encoder/binary'
|
||||
import * as _Encoder from './cif/encoder'
|
||||
import { ArrayEncoding, ArrayEncoder } from '../common/binary-cif';
|
||||
import { CifFrame } from '../reader/cif';
|
||||
@@ -20,7 +20,7 @@ export namespace CifWriter {
|
||||
export interface EncoderParams {
|
||||
binary?: boolean,
|
||||
encoderName?: string,
|
||||
binaryEncodingPovider?: EncodingProvider,
|
||||
binaryEncodingPovider?: BinaryEncodingProvider,
|
||||
binaryAutoClassifyEncoding?: boolean
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ export namespace CifWriter {
|
||||
return binary ? new BinaryEncoder(encoderName, params ? params.binaryEncodingPovider : void 0, params ? !!params.binaryAutoClassifyEncoding : false) : new TextEncoder();
|
||||
}
|
||||
|
||||
export function fields<K = number, D = any>() {
|
||||
return Field.build<K, D>();
|
||||
export function fields<K = number, D = any, N extends string = string>() {
|
||||
return Field.build<K, D, N>();
|
||||
}
|
||||
|
||||
import E = Encoding
|
||||
@@ -44,7 +44,7 @@ export namespace CifWriter {
|
||||
return { fields, source: [source] };
|
||||
}
|
||||
|
||||
export function createEncodingProviderFromCifFrame(frame: CifFrame): EncodingProvider {
|
||||
export function createEncodingProviderFromCifFrame(frame: CifFrame): BinaryEncodingProvider {
|
||||
return {
|
||||
get(c, f) {
|
||||
const cat = frame.categories[c];
|
||||
@@ -55,7 +55,7 @@ export namespace CifWriter {
|
||||
}
|
||||
};
|
||||
|
||||
export function createEncodingProviderFromJsonConfig(hints: EncodingStrategyHint[]): EncodingProvider {
|
||||
export function createEncodingProviderFromJsonConfig(hints: EncodingStrategyHint[]): BinaryEncodingProvider {
|
||||
return {
|
||||
get(c, f) {
|
||||
for (let i = 0; i < hints.length; i++) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Column, Table, Database, DatabaseCollection } from '../../../mol-data/d
|
||||
import { Tensor } from '../../../mol-math/linear-algebra'
|
||||
import EncoderBase from '../encoder'
|
||||
import { ArrayEncoder, ArrayEncoding } from '../../common/binary-cif';
|
||||
import { BinaryEncodingProvider } from './encoder/binary';
|
||||
|
||||
// TODO: support for "coordinate fields", make "coordinate precision" a parameter of the encoder
|
||||
// TODO: automatically detect "precision" of floating point arrays.
|
||||
@@ -72,25 +73,32 @@ export namespace Field {
|
||||
return int(name, (e, d, i) => i + 1, { typedArray: Int32Array, encoder: ArrayEncoding.by(ArrayEncoding.delta).and(ArrayEncoding.runLength).and(ArrayEncoding.integerPacking) })
|
||||
}
|
||||
|
||||
export class Builder<K = number, D = any> {
|
||||
export class Builder<K = number, D = any, N extends string = string> {
|
||||
private fields: Field<K, D>[] = [];
|
||||
|
||||
index(name: string) {
|
||||
index(name: N) {
|
||||
this.fields.push(Field.index(name));
|
||||
return this;
|
||||
}
|
||||
|
||||
str(name: string, value: (k: K, d: D, index: number) => string, params?: ParamsBase<K, D>) {
|
||||
str(name: N, value: (k: K, d: D, index: number) => string, params?: ParamsBase<K, D>) {
|
||||
this.fields.push(Field.str(name, value, params));
|
||||
return this;
|
||||
}
|
||||
|
||||
int(name: string, value: (k: K, d: D, index: number) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor }) {
|
||||
int(name: N, value: (k: K, d: D, index: number) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor }) {
|
||||
this.fields.push(Field.int(name, value, params));
|
||||
return this;
|
||||
}
|
||||
|
||||
float(name: string, value: (k: K, d: D, index: number) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor, digitCount?: number }) {
|
||||
vec(name: N, values: ((k: K, d: D, index: number) => number)[], params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor }) {
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
this.fields.push(Field.int(`${name}[${i + 1}]`, values[i], params));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
float(name: N, value: (k: K, d: D, index: number) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor, digitCount?: number }) {
|
||||
this.fields.push(Field.float(name, value, params));
|
||||
return this;
|
||||
}
|
||||
@@ -103,8 +111,8 @@ export namespace Field {
|
||||
getFields() { return this.fields; }
|
||||
}
|
||||
|
||||
export function build<K = number, D = any>() {
|
||||
return new Builder<K, D>();
|
||||
export function build<K = number, D = any, N extends string = string>() {
|
||||
return new Builder<K, D, N>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,14 +223,21 @@ export namespace Category {
|
||||
|
||||
export interface Encoder<T = string | Uint8Array> extends EncoderBase {
|
||||
setFilter(filter?: Category.Filter): void,
|
||||
isCategoryIncluded(name: string): boolean,
|
||||
setFormatter(formatter?: Category.Formatter): void,
|
||||
|
||||
startDataBlock(header: string): void,
|
||||
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx): void,
|
||||
getData(): T
|
||||
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx, options?: Encoder.WriteCategoryOptions): void,
|
||||
getData(): T,
|
||||
|
||||
binaryEncodingProvider: BinaryEncodingProvider | undefined;
|
||||
}
|
||||
|
||||
export namespace Encoder {
|
||||
export interface WriteCategoryOptions {
|
||||
ignoreFilter?: boolean
|
||||
}
|
||||
|
||||
export function writeDatabase(encoder: Encoder, name: string, database: Database<Database.Schema>) {
|
||||
encoder.startDataBlock(name);
|
||||
for (const table of database._tableNames) {
|
||||
|
||||
@@ -17,7 +17,7 @@ import { getIncludedFields, getCategoryInstanceData, CategoryInstanceData } from
|
||||
import { classifyIntArray, classifyFloatArray } from '../../../common/binary-cif/classifier';
|
||||
import { ArrayCtor } from '../../../../mol-util/type-helpers';
|
||||
|
||||
export interface EncodingProvider {
|
||||
export interface BinaryEncodingProvider {
|
||||
get(category: string, field: string): ArrayEncoder | undefined;
|
||||
}
|
||||
|
||||
@@ -28,10 +28,16 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
|
||||
private filter: Category.Filter = Category.DefaultFilter;
|
||||
private formatter: Category.Formatter = Category.DefaultFormatter;
|
||||
|
||||
binaryEncodingProvider: BinaryEncodingProvider | undefined = void 0;
|
||||
|
||||
setFilter(filter?: Category.Filter) {
|
||||
this.filter = filter || Category.DefaultFilter;
|
||||
}
|
||||
|
||||
isCategoryIncluded(name: string) {
|
||||
return this.filter.includeCategory(name);
|
||||
}
|
||||
|
||||
setFormatter(formatter?: Category.Formatter) {
|
||||
this.formatter = formatter || Category.DefaultFormatter;
|
||||
}
|
||||
@@ -43,7 +49,7 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
|
||||
});
|
||||
}
|
||||
|
||||
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx) {
|
||||
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx, options?: Encoder.WriteCategoryOptions) {
|
||||
if (!this.data) {
|
||||
throw new Error('The writer contents have already been encoded, no more writing.');
|
||||
}
|
||||
@@ -52,7 +58,7 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
|
||||
throw new Error('No data block created.');
|
||||
}
|
||||
|
||||
if (!this.filter.includeCategory(category.name)) return;
|
||||
if (!options?.ignoreFilter && !this.filter.includeCategory(category.name)) return;
|
||||
|
||||
const { instance, rowCount, source } = getCategoryInstanceData(category, context);
|
||||
if (!rowCount) return;
|
||||
@@ -64,7 +70,7 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
|
||||
if (!this.filter.includeField(category.name, f.name)) continue;
|
||||
|
||||
const format = this.formatter.getFormat(category.name, f.name);
|
||||
cat.columns.push(encodeField(category.name, f, source, rowCount, format, this.encodingProvider, this.autoClassify));
|
||||
cat.columns.push(encodeField(category.name, f, source, rowCount, format, this.binaryEncodingProvider, this.autoClassify));
|
||||
}
|
||||
// no columns included.
|
||||
if (!cat.columns.length) return;
|
||||
@@ -88,7 +94,8 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
|
||||
return this.encodedData;
|
||||
}
|
||||
|
||||
constructor(encoder: string, private encodingProvider: EncodingProvider | undefined, private autoClassify: boolean) {
|
||||
constructor(encoder: string, encodingProvider: BinaryEncodingProvider | undefined, private autoClassify: boolean) {
|
||||
this.binaryEncodingProvider = encodingProvider;
|
||||
this.data = {
|
||||
encoder,
|
||||
version: VERSION,
|
||||
@@ -110,7 +117,7 @@ function getDefaultEncoder(type: Field.Type): ArrayEncoder {
|
||||
return ArrayEncoder.by(E.byteArray);
|
||||
}
|
||||
|
||||
function tryGetEncoder(categoryName: string, field: Field, format: Field.Format | undefined, provider: EncodingProvider | undefined) {
|
||||
function tryGetEncoder(categoryName: string, field: Field, format: Field.Format | undefined, provider: BinaryEncodingProvider | undefined) {
|
||||
if (format && format.encoder) {
|
||||
return format.encoder;
|
||||
} else if (field.defaultFormat && field.defaultFormat.encoder) {
|
||||
@@ -129,7 +136,7 @@ function classify(type: Field.Type, data: ArrayLike<any>) {
|
||||
}
|
||||
|
||||
function encodeField(categoryName: string, field: Field, data: CategoryInstanceData['source'], totalCount: number,
|
||||
format: Field.Format | undefined, encoderProvider: EncodingProvider | undefined, autoClassify: boolean): EncodedColumn {
|
||||
format: Field.Format | undefined, encoderProvider: BinaryEncodingProvider | undefined, autoClassify: boolean): EncodedColumn {
|
||||
|
||||
const { array, allPresent, mask } = getFieldData(field, getArrayCtor(field, format), totalCount, data);
|
||||
|
||||
|
||||
@@ -19,10 +19,16 @@ export default class TextEncoder implements Encoder<string> {
|
||||
private filter: Category.Filter = Category.DefaultFilter;
|
||||
private formatter: Category.Formatter = Category.DefaultFormatter;
|
||||
|
||||
binaryEncodingProvider = void 0;
|
||||
|
||||
setFilter(filter?: Category.Filter) {
|
||||
this.filter = filter || Category.DefaultFilter;
|
||||
}
|
||||
|
||||
isCategoryIncluded(name: string) {
|
||||
return this.filter.includeCategory(name);
|
||||
}
|
||||
|
||||
setFormatter(formatter?: Category.Formatter) {
|
||||
this.formatter = formatter || Category.DefaultFormatter;
|
||||
}
|
||||
@@ -32,7 +38,7 @@ export default class TextEncoder implements Encoder<string> {
|
||||
StringBuilder.write(this.builder, `data_${(header || '').replace(/[ \n\t]/g, '').toUpperCase()}\n#\n`);
|
||||
}
|
||||
|
||||
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx) {
|
||||
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx, options?: Encoder.WriteCategoryOptions) {
|
||||
if (this.encoded) {
|
||||
throw new Error('The writer contents have already been encoded, no more writing.');
|
||||
}
|
||||
@@ -41,7 +47,7 @@ export default class TextEncoder implements Encoder<string> {
|
||||
throw new Error('No data block created.');
|
||||
}
|
||||
|
||||
if (!this.filter.includeCategory(category.name)) return;
|
||||
if (!options?.ignoreFilter && !this.filter.includeCategory(category.name)) return;
|
||||
const { instance, rowCount, source } = getCategoryInstanceData(category, context);
|
||||
if (!rowCount) return;
|
||||
|
||||
|
||||
@@ -45,13 +45,23 @@ export class BoundaryHelper {
|
||||
}
|
||||
}
|
||||
|
||||
includeStep(p: Vec3) {
|
||||
includeSphere(s: Sphere3D) {
|
||||
if (Sphere3D.hasExtrema(s)) {
|
||||
for (const e of s.extrema) {
|
||||
this.includePosition(e);
|
||||
}
|
||||
} else {
|
||||
this.includePositionRadius(s.center, s.radius);
|
||||
}
|
||||
}
|
||||
|
||||
includePosition(p: Vec3) {
|
||||
for (let i = 0, il = this.dir.length; i < il; ++i) {
|
||||
this.computeExtrema(i, p)
|
||||
}
|
||||
}
|
||||
|
||||
includeSphereStep(center: Vec3, radius: number) {
|
||||
includePositionRadius(center: Vec3, radius: number) {
|
||||
for (let i = 0, il = this.dir.length; i < il; ++i) {
|
||||
this.computeSphereExtrema(i, center, radius)
|
||||
}
|
||||
@@ -64,11 +74,21 @@ export class BoundaryHelper {
|
||||
this.centroidHelper.finishedIncludeStep();
|
||||
}
|
||||
|
||||
radiusStep(p: Vec3) {
|
||||
radiusSphere(s: Sphere3D) {
|
||||
if (Sphere3D.hasExtrema(s)) {
|
||||
for (const e of s.extrema) {
|
||||
this.radiusPosition(e)
|
||||
}
|
||||
} else {
|
||||
this.radiusPositionRadius(s.center, s.radius);
|
||||
}
|
||||
}
|
||||
|
||||
radiusPosition(p: Vec3) {
|
||||
this.centroidHelper.radiusStep(p);
|
||||
}
|
||||
|
||||
radiusSphereStep(center: Vec3, radius: number) {
|
||||
radiusPositionRadius(center: Vec3, radius: number) {
|
||||
this.centroidHelper.radiusSphereStep(center, radius);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,13 +28,13 @@ export function getBoundary(data: PositionData): Boundary {
|
||||
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.includePositionRadius(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);
|
||||
boundaryHelper.radiusPositionRadius(p, (radius && radius[i]) || 0);
|
||||
}
|
||||
|
||||
const sphere = boundaryHelper.getSphere()
|
||||
|
||||
@@ -104,9 +104,9 @@ namespace Spacegroup {
|
||||
);
|
||||
}
|
||||
|
||||
export function getSymmetryOperator(spacegroup: Spacegroup, index: number, i: number, j: number, k: number): SymmetryOperator {
|
||||
const operator = setOperatorMatrix(spacegroup, index, i, j, k, Mat4.zero());
|
||||
return SymmetryOperator.create(`${index + 1}_${5 + i}${5 + j}${5 + k}`, operator, { id: '', operList: [] }, '', Vec3.create(i, j, k), index);
|
||||
export function getSymmetryOperator(spacegroup: Spacegroup, spgrOp: number, i: number, j: number, k: number): SymmetryOperator {
|
||||
const operator = setOperatorMatrix(spacegroup, spgrOp, i, j, k, Mat4.zero());
|
||||
return SymmetryOperator.create(`${spgrOp + 1}_${5 + i}${5 + j}${5 + k}`, operator, { hkl: Vec3.create(i, j, k), spgrOp });
|
||||
}
|
||||
|
||||
const _translationRef = Vec3()
|
||||
@@ -135,9 +135,9 @@ namespace Spacegroup {
|
||||
* Get Symmetry operator for transformation around the given
|
||||
* reference point `ref` in fractional coordinates
|
||||
*/
|
||||
export function getSymmetryOperatorRef(spacegroup: Spacegroup, index: number, i: number, j: number, k: number, ref: Vec3) {
|
||||
const operator = setOperatorMatrixRef(spacegroup, index, i, j, k, ref, Mat4.zero());
|
||||
return SymmetryOperator.create(`${index + 1}_${5 + i}${5 + j}${5 + k}`, operator, { id: '', operList: [] }, '', Vec3.create(i, j, k), index);
|
||||
export function getSymmetryOperatorRef(spacegroup: Spacegroup, spgrOp: number, i: number, j: number, k: number, ref: Vec3) {
|
||||
const operator = setOperatorMatrixRef(spacegroup, spgrOp, i, j, k, ref, Mat4.zero());
|
||||
return SymmetryOperator.create(`${spgrOp + 1}_${5 + i}${5 + j}${5 + k}`, operator, { hkl: Vec3.create(i, j, k), spgrOp });
|
||||
}
|
||||
|
||||
function getOperatorMatrix(ids: number[]) {
|
||||
|
||||
@@ -11,12 +11,14 @@ import { defaults } from '../../mol-util';
|
||||
interface SymmetryOperator {
|
||||
readonly name: string,
|
||||
|
||||
readonly assembly: {
|
||||
readonly assembly?: {
|
||||
/** pointer to `pdbx_struct_assembly.id` or empty string */
|
||||
readonly id: string
|
||||
readonly id: string,
|
||||
/** pointers to `pdbx_struct_oper_list.id` or empty list */
|
||||
readonly operList: string[]
|
||||
}
|
||||
readonly operList: string[],
|
||||
/** (arbitrary) unique id of the operator to be used in suffix */
|
||||
readonly operId: number
|
||||
},
|
||||
|
||||
/** pointer to `struct_ncs_oper.id` or empty string */
|
||||
readonly ncsId: string,
|
||||
@@ -29,22 +31,52 @@ interface SymmetryOperator {
|
||||
// cache the inverse of the transform
|
||||
readonly inverse: Mat4,
|
||||
// optimize the identity case
|
||||
readonly isIdentity: boolean
|
||||
readonly isIdentity: boolean,
|
||||
|
||||
/**
|
||||
* Suffix based on operator type.
|
||||
* - Assembly: _assembly.operId
|
||||
* - Crytal: -op_ijk
|
||||
* - ncs: _ncsId
|
||||
*/
|
||||
readonly suffix: string
|
||||
}
|
||||
|
||||
namespace SymmetryOperator {
|
||||
export const DefaultName = '1_555'
|
||||
export const Default: SymmetryOperator = create(DefaultName, Mat4.identity(), { id: '', operList: [] });
|
||||
export const Default: SymmetryOperator = create(DefaultName, Mat4.identity());
|
||||
|
||||
export const RotationTranslationEpsilon = 0.005;
|
||||
|
||||
export function create(name: string, matrix: Mat4, assembly: SymmetryOperator['assembly'], ncsId?: string, hkl?: Vec3, spgrOp?: number): SymmetryOperator {
|
||||
export type CreateInfo = { assembly?: SymmetryOperator['assembly'], ncsId?: string, hkl?: Vec3, spgrOp?: number }
|
||||
export function create(name: string, matrix: Mat4, info?: CreateInfo): SymmetryOperator {
|
||||
let { assembly, ncsId, hkl, spgrOp } = info || { };
|
||||
const _hkl = hkl ? Vec3.clone(hkl) : Vec3.zero();
|
||||
spgrOp = defaults(spgrOp, -1)
|
||||
ncsId = ncsId || ''
|
||||
if (Mat4.isIdentity(matrix)) return { name, assembly, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl, spgrOp, ncsId };
|
||||
spgrOp = defaults(spgrOp, -1);
|
||||
ncsId = ncsId || '';
|
||||
const suffix = getSuffix(info);
|
||||
if (Mat4.isIdentity(matrix)) return { name, assembly, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl, spgrOp, ncsId, suffix };
|
||||
if (!Mat4.isRotationAndTranslation(matrix, RotationTranslationEpsilon)) throw new Error(`Symmetry operator (${name}) must be a composition of rotation and translation.`);
|
||||
return { name, assembly, matrix, inverse: Mat4.invert(Mat4.zero(), matrix), isIdentity: false, hkl: _hkl, spgrOp, ncsId };
|
||||
return { name, assembly, matrix, inverse: Mat4.invert(Mat4.zero(), matrix), isIdentity: false, hkl: _hkl, spgrOp, ncsId, suffix };
|
||||
}
|
||||
|
||||
function getSuffix(info?: CreateInfo) {
|
||||
if (!info) return '';
|
||||
|
||||
if (info.assembly) {
|
||||
return `_${info.assembly.operId}`;
|
||||
}
|
||||
|
||||
if (typeof info.spgrOp !== 'undefined' && typeof info.hkl !== 'undefined' && info.spgrOp !== -1) {
|
||||
const [i, j, k] = info.hkl;
|
||||
return `-${info.spgrOp + 1}_${5 + i}${5 + j}${5 + k}`
|
||||
}
|
||||
|
||||
if (info.ncsId) {
|
||||
return `_${info.ncsId}`;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
export function checkIfRotationAndTranslation(rot: Mat3, offset: Vec3) {
|
||||
@@ -66,7 +98,7 @@ namespace SymmetryOperator {
|
||||
}
|
||||
}
|
||||
Mat4.setTranslation(t, offset);
|
||||
return create(name, t, { id: '', operList: [] }, ncsId);
|
||||
return create(name, t, { ncsId });
|
||||
}
|
||||
|
||||
const _q1 = Quat.identity(), _q2 = Quat.zero(), _q3 = Quat.zero(), _axis = Vec3.zero();
|
||||
@@ -114,7 +146,7 @@ namespace SymmetryOperator {
|
||||
*/
|
||||
export function compose(first: SymmetryOperator, second: SymmetryOperator) {
|
||||
const matrix = Mat4.mul(Mat4.zero(), second.matrix, first.matrix);
|
||||
return create(second.name, matrix, second.assembly, second.ncsId, second.hkl, second.spgrOp);
|
||||
return create(second.name, matrix, second);
|
||||
}
|
||||
|
||||
export interface CoordinateMapper<T extends number> { (index: T, slot: Vec3): Vec3 }
|
||||
|
||||
@@ -254,6 +254,31 @@ namespace Mat4 {
|
||||
return out;
|
||||
}
|
||||
|
||||
export function extractRotation(out: Mat4, mat: Mat4) {
|
||||
const scaleX = 1 / Math.sqrt(mat[0] * mat[0] + mat[1] * mat[1] + mat[2] * mat[2]);
|
||||
const scaleY = 1 / Math.sqrt(mat[4] * mat[4] + mat[5] * mat[5] + mat[6] * mat[6]);
|
||||
const scaleZ = 1 / Math.sqrt(mat[8] * mat[8] + mat[9] * mat[9] + mat[10] * mat[10]);
|
||||
|
||||
out[0] = mat[0] * scaleX;
|
||||
out[1] = mat[1] * scaleX;
|
||||
out[2] = mat[2] * scaleX;
|
||||
out[3] = 0;
|
||||
out[4] = mat[4] * scaleY;
|
||||
out[5] = mat[5] * scaleY;
|
||||
out[6] = mat[6] * scaleY;
|
||||
out[7] = 0;
|
||||
out[8] = mat[8] * scaleZ;
|
||||
out[9] = mat[9] * scaleZ;
|
||||
out[10] = mat[10] * scaleZ;
|
||||
out[11] = 0;
|
||||
out[12] = 0;
|
||||
out[13] = 0;
|
||||
out[14] = 0;
|
||||
out[15] = 1;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
export function transpose(out: Mat4, a: Mat4) {
|
||||
// If we are transposing ourselves we can skip a few steps but have to cache some values
|
||||
if (out === a) {
|
||||
|
||||
@@ -546,6 +546,8 @@ namespace Vec3 {
|
||||
return `[${a[0].toPrecision(precision)} ${a[1].toPrecision(precision)} ${a[2].toPrecision(precision)}]`;
|
||||
}
|
||||
|
||||
export const origin: ReadonlyVec3 = Vec3.create(0, 0, 0)
|
||||
|
||||
export const unit: ReadonlyVec3 = Vec3.create(1, 1, 1)
|
||||
export const negUnit: ReadonlyVec3 = Vec3.create(-1, -1, -1)
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace PrincipalAxes {
|
||||
|
||||
export function calculateMomentsAxes(positions: NumberArray): Axes3D {
|
||||
if (positions.length === 3) {
|
||||
return Axes3D.create(Vec3.fromArray(Vec3(), positions, 0), Vec3(), Vec3(), Vec3())
|
||||
return Axes3D.create(Vec3.fromArray(Vec3(), positions, 0), Vec3.create(1, 0, 0), Vec3.create(0, 1, 0), Vec3.create(0, 1, 0))
|
||||
}
|
||||
|
||||
const points = Matrix.fromArray(positions, 3, positions.length / 3)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import { Column, Table } from '../../../mol-data/db';
|
||||
import { Interval, Segmentation } from '../../../mol-data/int';
|
||||
import UUID from '../../../mol-util/uuid';
|
||||
import { ElementIndex } from '../../../mol-model/structure';
|
||||
import { ElementIndex, ChainIndex } from '../../../mol-model/structure';
|
||||
import { Model } from '../../../mol-model/structure/model/model';
|
||||
import { AtomicConformation, AtomicData, AtomicHierarchy, AtomicSegments, AtomsSchema, ChainsSchema, ResiduesSchema } from '../../../mol-model/structure/model/properties/atomic';
|
||||
import { getAtomicIndex } from '../../../mol-model/structure/model/properties/utils/atomic-index';
|
||||
@@ -16,6 +16,12 @@ import { ElementSymbol } from '../../../mol-model/structure/model/types';
|
||||
import { Entities } from '../../../mol-model/structure/model/properties/common';
|
||||
import { getAtomicDerivedData } from '../../../mol-model/structure/model/properties/utils/atomic-derived';
|
||||
import { AtomSite } from './schema';
|
||||
import { ModelFormat } from '../format';
|
||||
import { SymmetryOperator } from '../../../mol-math/geometry';
|
||||
import { MmcifFormat } from '../mmcif';
|
||||
import { AtomSiteOperatorMappingSchema } from '../../../mol-model/structure/export/categories/atom_site_operator_mapping';
|
||||
import { toDatabase } from '../../../mol-io/reader/cif/schema';
|
||||
import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
|
||||
|
||||
function findHierarchyOffsets(atom_site: AtomSite) {
|
||||
if (atom_site._rowCount === 0) return { residues: [], chains: [] };
|
||||
@@ -95,14 +101,70 @@ function isHierarchyDataEqual(a: AtomicData, b: AtomicData) {
|
||||
&& Table.areEqual(a.atoms, b.atoms)
|
||||
}
|
||||
|
||||
function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], previous?: Model) {
|
||||
function createChainOperatorMappingAndSubstituteNames(hierarchy: AtomicData, format: ModelFormat) {
|
||||
const mapping = new Map<ChainIndex, SymmetryOperator>();
|
||||
if (!MmcifFormat.is(format)) return mapping;
|
||||
|
||||
const { molstar_atom_site_operator_mapping: entries } = toDatabase(AtomSiteOperatorMappingSchema, format.data.frame);
|
||||
if (entries._rowCount === 0) return mapping;
|
||||
|
||||
const labelMap = new Map<string, { name: string, operator: SymmetryOperator }>();
|
||||
const authMap = new Map<string, string>();
|
||||
|
||||
for (let i = 0; i < entries._rowCount; i++) {
|
||||
const assembly: SymmetryOperator['assembly'] = entries.assembly_operator_id.valueKind(i) === Column.ValueKind.Present
|
||||
? { id: entries.assembly_id.value(i), operList: [], operId: entries.assembly_operator_id.value(i) }
|
||||
: void 0;
|
||||
|
||||
const operator = SymmetryOperator.create(entries.operator_name.value(i), Mat4.identity(), {
|
||||
assembly,
|
||||
spgrOp: entries.symmetry_operator_index.valueKind(i) === Column.ValueKind.Present ? entries.symmetry_operator_index.value(i) : void 0,
|
||||
hkl: Vec3.ofArray(entries.symmetry_hkl.value(i)),
|
||||
ncsId: entries.ncs_id.value(i)
|
||||
});
|
||||
|
||||
const suffix = entries.suffix.value(i);
|
||||
const label = entries.label_asym_id.value(i);
|
||||
labelMap.set(`${label}${suffix}`, { name: label, operator });
|
||||
const auth = entries.auth_asym_id.value(i);
|
||||
authMap.set(`${auth}${suffix}`, auth);
|
||||
}
|
||||
|
||||
const { label_asym_id, auth_asym_id } = hierarchy.chains;
|
||||
const mappedLabel: string[] = new Array(label_asym_id.rowCount);
|
||||
const mappedAuth: string[] = new Array(label_asym_id.rowCount);
|
||||
|
||||
for (let i = 0 as ChainIndex; i < label_asym_id.rowCount; i++) {
|
||||
const label = label_asym_id.value(i), auth = auth_asym_id.value(i);
|
||||
if (!labelMap.has(label)) {
|
||||
mappedLabel[i] = label;
|
||||
mappedAuth[i] = auth;
|
||||
continue;
|
||||
}
|
||||
|
||||
const { name, operator } = labelMap.get(label)!;
|
||||
mapping.set(i, operator);
|
||||
|
||||
mappedLabel[i] = name;
|
||||
mappedAuth[i] = authMap.get(auth) || auth;
|
||||
}
|
||||
|
||||
hierarchy.chains.label_asym_id = Column.ofArray({ array: mappedLabel, valueKind: hierarchy.chains.label_asym_id.valueKind, schema: hierarchy.chains.label_asym_id.schema });
|
||||
hierarchy.chains.auth_asym_id = Column.ofArray({ array: mappedAuth, valueKind: hierarchy.chains.auth_asym_id.valueKind, schema: hierarchy.chains.auth_asym_id.schema });
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], format: ModelFormat, previous?: Model) {
|
||||
const hierarchyOffsets = findHierarchyOffsets(atom_site);
|
||||
const hierarchyData = createHierarchyData(atom_site, sourceIndex, hierarchyOffsets);
|
||||
const chainOperatorMapping = createChainOperatorMappingAndSubstituteNames(hierarchyData, format);
|
||||
|
||||
if (previous && isHierarchyDataEqual(previous.atomicHierarchy, hierarchyData)) {
|
||||
return {
|
||||
sameAsPrevious: true,
|
||||
hierarchy: previous.atomicHierarchy,
|
||||
chainOperatorMapping
|
||||
};
|
||||
}
|
||||
|
||||
@@ -114,11 +176,11 @@ function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, en
|
||||
const index = getAtomicIndex(hierarchyData, entities, hierarchySegments);
|
||||
const derived = getAtomicDerivedData(hierarchyData, index, chemicalComponentMap);
|
||||
const hierarchy: AtomicHierarchy = { ...hierarchyData, ...hierarchySegments, index, derived };
|
||||
return { sameAsPrevious: false, hierarchy };
|
||||
return { sameAsPrevious: false, hierarchy, chainOperatorMapping };
|
||||
}
|
||||
|
||||
export function getAtomicHierarchyAndConformation(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], previous?: Model) {
|
||||
const { sameAsPrevious, hierarchy } = getAtomicHierarchy(atom_site, sourceIndex, entities, chemicalComponentMap, previous)
|
||||
export function getAtomicHierarchyAndConformation(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], format: ModelFormat, previous?: Model) {
|
||||
const { sameAsPrevious, hierarchy, chainOperatorMapping } = getAtomicHierarchy(atom_site, sourceIndex, entities, chemicalComponentMap, format, previous)
|
||||
const conformation = getConformation(atom_site)
|
||||
return { sameAsPrevious, hierarchy, conformation };
|
||||
return { sameAsPrevious, hierarchy, conformation, chainOperatorMapping };
|
||||
}
|
||||
@@ -39,7 +39,7 @@ export async function createModels(data: BasicData, format: ModelFormat, ctx: Ru
|
||||
/** Standard atomic model */
|
||||
function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, properties: Model['properties'], format: ModelFormat, previous?: Model): Model {
|
||||
|
||||
const atomic = getAtomicHierarchyAndConformation(atom_site, sourceIndex, entities, properties.chemicalComponentMap, previous);
|
||||
const atomic = getAtomicHierarchyAndConformation(atom_site, sourceIndex, entities, properties.chemicalComponentMap, format, previous);
|
||||
const modelNum = atom_site.pdbx_PDB_model_num.value(0)
|
||||
if (previous && atomic.sameAsPrevious) {
|
||||
return {
|
||||
@@ -75,6 +75,7 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
|
||||
atomicHierarchy: atomic.hierarchy,
|
||||
atomicConformation: atomic.conformation,
|
||||
atomicRanges,
|
||||
atomicChainOperatorMappinng: atomic.chainOperatorMapping,
|
||||
coarseHierarchy: coarse.hierarchy,
|
||||
coarseConformation: coarse.conformation,
|
||||
properties,
|
||||
@@ -86,7 +87,7 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
|
||||
|
||||
/** Integrative model with atomic/coarse parts */
|
||||
function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Model['properties'], format: ModelFormat): Model {
|
||||
const atomic = getAtomicHierarchyAndConformation(ihm.atom_site, ihm.atom_site_sourceIndex, ihm.entities, properties.chemicalComponentMap);
|
||||
const atomic = getAtomicHierarchyAndConformation(ihm.atom_site, ihm.atom_site_sourceIndex, ihm.entities, properties.chemicalComponentMap, format);
|
||||
const coarse = getCoarse(ihm, properties);
|
||||
const sequence = getSequence(data, ihm.entities, atomic.hierarchy, coarse.hierarchy)
|
||||
const atomicRanges = getAtomicRanges(atomic.hierarchy, ihm.entities, atomic.conformation, sequence)
|
||||
@@ -113,6 +114,7 @@ function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Mo
|
||||
atomicHierarchy: atomic.hierarchy,
|
||||
atomicConformation: atomic.conformation,
|
||||
atomicRanges,
|
||||
atomicChainOperatorMappinng: atomic.chainOperatorMapping,
|
||||
coarseHierarchy: coarse.hierarchy,
|
||||
coarseConformation: coarse.conformation,
|
||||
properties,
|
||||
|
||||
@@ -117,7 +117,7 @@ function getAssemblyOperators(matrices: Matrices, operatorNames: string[][], sta
|
||||
Mat4.mul(m, m, matrices.get(op[i])!);
|
||||
}
|
||||
index++
|
||||
operators[operators.length] = SymmetryOperator.create(`ASM_${index}`, m, { id: assemblyId, operList: op });
|
||||
operators[operators.length] = SymmetryOperator.create(`ASM_${index}`, m, { assembly: { id: assemblyId, operId: index, operList: op } });
|
||||
}
|
||||
|
||||
return operators;
|
||||
|
||||
@@ -107,7 +107,7 @@ namespace CustomElementProperty {
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && !!modelProperty.get(ctx.structure.models[0]).value,
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? modelProperty.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (_, data: ThemeDataContext) => data.structure && data.structure.models[0].customProperties.reference(modelProperty.descriptor, false)
|
||||
detach: (data: ThemeDataContext) => data.structure && data.structure.models[0].customProperties.reference(modelProperty.descriptor, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,7 +116,7 @@ namespace CustomElementProperty {
|
||||
return function(loci: Loci): string | undefined {
|
||||
if (loci.kind === 'element-loci') {
|
||||
const e = loci.elements[0];
|
||||
if (!e) return
|
||||
if (!e || !e.unit.model.customProperties.hasReference(modelProperty.descriptor)) return
|
||||
const data = modelProperty.get(e.unit.model).value
|
||||
const element = e.unit.elements[OrderedSet.start(e.indices)]
|
||||
const value = data?.get(element)
|
||||
|
||||
@@ -69,7 +69,8 @@ namespace CustomModelProperty {
|
||||
// this invalidates property.value
|
||||
set(data, p, undefined)
|
||||
}
|
||||
}
|
||||
},
|
||||
props: (data: Model) => get(data).props,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ namespace CustomProperty {
|
||||
readonly ref: (data: Data, add: boolean) => void
|
||||
readonly get: (data: Data) => ValueBox<Value | undefined>
|
||||
readonly set: (data: Data, props: PD.Values<Params>, value?: Value) => void
|
||||
readonly props: (data: Data) => PD.Values<Params>
|
||||
}
|
||||
|
||||
export class Registry<Data> {
|
||||
|
||||
@@ -72,7 +72,8 @@ namespace CustomStructureProperty {
|
||||
// this invalidates property.value
|
||||
set(data, p, value)
|
||||
}
|
||||
}
|
||||
},
|
||||
props: (data: Structure) => get(data).props,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,6 @@ export const InteractionsRepresentationProvider = StructureRepresentationProvide
|
||||
isApplicable: (structure: Structure) => structure.elementCount > 0,
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, structure: Structure) => InteractionsProvider.attach(ctx, structure, void 0, true),
|
||||
detach: (_, data) => InteractionsProvider.ref(data, false)
|
||||
detach: (data) => InteractionsProvider.ref(data, false)
|
||||
}
|
||||
})
|
||||
@@ -5,7 +5,6 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ColorListName, ColorListOptionsScale } from '../../../mol-util/color/lists'
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition'
|
||||
import { Color, ColorScale } from '../../../mol-util/color'
|
||||
import { ThemeDataContext } from '../../../mol-theme/theme'
|
||||
@@ -20,7 +19,7 @@ const DefaultColor = Color(0xFAFAFA)
|
||||
const Description = 'Assigns a color based on the relative accessible surface area of a residue.'
|
||||
|
||||
export const AccessibleSurfaceAreaColorThemeParams = {
|
||||
list: PD.ColorList<ColorListName>('rainbow', ColorListOptionsScale)
|
||||
list: PD.ColorList('rainbow', { presetKind: 'scale' })
|
||||
}
|
||||
export type AccessibleSurfaceAreaColorThemeParams = typeof AccessibleSurfaceAreaColorThemeParams
|
||||
export function getAccessibleSurfaceAreaColorThemeParams(ctx: ThemeDataContext) {
|
||||
@@ -30,7 +29,7 @@ export function AccessibleSurfaceAreaColorTheme(ctx: ThemeDataContext, props: PD
|
||||
let color: LocationColor
|
||||
|
||||
const scale = ColorScale.create({
|
||||
listOrName: props.list,
|
||||
listOrName: props.list.colors,
|
||||
minLabel: 'buried',
|
||||
maxLabel: 'exposed',
|
||||
domain: [0.0, 1.0]
|
||||
@@ -74,6 +73,6 @@ export const AccessibleSurfaceAreaColorThemeProvider: ColorTheme.Provider<Access
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure,
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? AccessibleSurfaceAreaProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
|
||||
detach: (_, data) => data.structure && data.structure.customPropertyDescriptors.reference(AccessibleSurfaceAreaProvider.descriptor, false)
|
||||
detach: (data) => data.structure && data.structure.customPropertyDescriptors.reference(AccessibleSurfaceAreaProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
@@ -116,6 +116,6 @@ export const InteractionTypeColorThemeProvider: ColorTheme.Provider<InteractionT
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure,
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? InteractionsProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
|
||||
detach: (_, data) => data.structure && data.structure.customPropertyDescriptors.reference(InteractionsProvider.descriptor, false)
|
||||
detach: (data) => data.structure && data.structure.customPropertyDescriptors.reference(InteractionsProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import { Color, ColorScale } from '../../../mol-util/color';
|
||||
import { Location } from '../../../mol-model/location';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition'
|
||||
import { ThemeDataContext } from '../../../mol-theme/theme';
|
||||
import { ColorListName, ColorListOptionsScale } from '../../../mol-util/color/lists';
|
||||
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
|
||||
import { CustomProperty } from '../../common/custom-property';
|
||||
import { CrossLinkRestraintProvider, CrossLinkRestraint } from './property';
|
||||
@@ -18,7 +17,7 @@ const Description = 'Colors cross-links by the deviation of the observed distanc
|
||||
|
||||
export const CrossLinkColorThemeParams = {
|
||||
domain: PD.Interval([0.5, 1.5], { step: 0.01 }),
|
||||
list: PD.ColorList<ColorListName>('red-grey', ColorListOptionsScale),
|
||||
list: PD.ColorList('red-grey', { presetKind: 'scale' }),
|
||||
}
|
||||
export type CrossLinkColorThemeParams = typeof CrossLinkColorThemeParams
|
||||
export function getCrossLinkColorThemeParams(ctx: ThemeDataContext) {
|
||||
@@ -34,7 +33,7 @@ export function CrossLinkColorTheme(ctx: ThemeDataContext, props: PD.Values<Cros
|
||||
if (crossLinkRestraints) {
|
||||
scale = ColorScale.create({
|
||||
domain: props.domain,
|
||||
listOrName: props.list
|
||||
listOrName: props.list.colors
|
||||
})
|
||||
const scaleColor = scale.color
|
||||
|
||||
@@ -71,6 +70,6 @@ export const CrossLinkColorThemeProvider: ColorTheme.Provider<CrossLinkColorThem
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && CrossLinkRestraint.isApplicable(ctx.structure),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? CrossLinkRestraintProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
|
||||
detach: (_, data) => data.structure && data.structure.customPropertyDescriptors.reference(CrossLinkRestraintProvider.descriptor, false)
|
||||
}
|
||||
detach: (data) => data.structure && data.structure.customPropertyDescriptors.reference(CrossLinkRestraintProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
@@ -144,6 +144,6 @@ export const CrossLinkRestraintRepresentationProvider = StructureRepresentationP
|
||||
isApplicable: (structure: Structure) => CrossLinkRestraint.isApplicable(structure),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, structure: Structure) => CrossLinkRestraintProvider.attach(ctx, structure, void 0, true),
|
||||
detach: (_, data) => CrossLinkRestraintProvider.ref(data, false)
|
||||
detach: (data) => CrossLinkRestraintProvider.ref(data, false)
|
||||
}
|
||||
})
|
||||
@@ -105,6 +105,6 @@ export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Param
|
||||
isApplicable: (ctx: ThemeDataContext) => StructureQualityReport.isApplicable(ctx.structure?.models[0]),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? StructureQualityReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (_, data) => data.structure && data.structure.models[0].customProperties.reference(StructureQualityReportProvider.descriptor, false)
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(StructureQualityReportProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ export namespace AssemblySymmetry {
|
||||
// check if assembly is 'biological'
|
||||
const mmcif = structure.models[0].sourceData.data.db
|
||||
if (!mmcif.pdbx_struct_assembly.details.isDefined) return false
|
||||
const id = structure.units[0].conformation.operator.assembly.id
|
||||
const id = structure.units[0].conformation.operator.assembly?.id || ''
|
||||
if (id === '' || id === 'deposited') return true
|
||||
const indices = Column.indicesOf(mmcif.pdbx_struct_assembly.id, e => e === id)
|
||||
if (indices.length !== 1) return false
|
||||
@@ -50,12 +50,12 @@ export namespace AssemblySymmetry {
|
||||
return BiologicalAssemblyNames.has(details)
|
||||
}
|
||||
|
||||
export async function fetch(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryProps): Promise<AssemblySymmetryValue> {
|
||||
export async function fetch(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryDataProps): Promise<AssemblySymmetryDataValue> {
|
||||
if (!isApplicable(structure)) return []
|
||||
|
||||
const client = new GraphQLClient(props.serverUrl, ctx.fetch)
|
||||
const variables: AssemblySymmetryQueryVariables = {
|
||||
assembly_id: structure.units[0].conformation.operator.assembly.id || 'deposited',
|
||||
assembly_id: structure.units[0].conformation.operator.assembly?.id || 'deposited',
|
||||
entry_id: structure.units[0].model.entryId
|
||||
}
|
||||
const result = await client.request<AssemblySymmetryQuery>(ctx.runtime, query, variables)
|
||||
@@ -64,23 +64,23 @@ export namespace AssemblySymmetry {
|
||||
console.error('expected `rcsb_struct_symmetry` field')
|
||||
return []
|
||||
}
|
||||
return result.assembly.rcsb_struct_symmetry as AssemblySymmetryValue
|
||||
return result.assembly.rcsb_struct_symmetry as AssemblySymmetryDataValue
|
||||
}
|
||||
|
||||
export type RotationAxes = ReadonlyArray<{ order: number, start: ReadonlyVec3, end: ReadonlyVec3 }>
|
||||
export function isRotationAxes(x: AssemblySymmetryValue[0]['rotation_axes']): x is RotationAxes {
|
||||
export function isRotationAxes(x: AssemblySymmetryValue['rotation_axes']): x is RotationAxes {
|
||||
return !!x && x.length > 0
|
||||
}
|
||||
}
|
||||
|
||||
export function getSymmetrySelectParam(structure?: Structure) {
|
||||
const param = PD.Select<number>(-1, [[-1, 'No Symmetries']])
|
||||
const param = PD.Select<number>(0, [[0, 'First Symmetry']])
|
||||
if (structure) {
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(structure).value
|
||||
if (assemblySymmetry) {
|
||||
const assemblySymmetryData = AssemblySymmetryDataProvider.get(structure).value
|
||||
if (assemblySymmetryData) {
|
||||
const options: [number, string][] = []
|
||||
for (let i = 0, il = assemblySymmetry.length; i < il; ++i) {
|
||||
const { symbol, kind } = assemblySymmetry[i]
|
||||
for (let i = 0, il = assemblySymmetryData.length; i < il; ++i) {
|
||||
const { symbol, kind } = assemblySymmetryData[i]
|
||||
if (symbol !== 'C1') {
|
||||
options.push([ i, `${i + 1}: ${symbol} ${kind}` ])
|
||||
}
|
||||
@@ -94,13 +94,46 @@ export function getSymmetrySelectParam(structure?: Structure) {
|
||||
return param
|
||||
}
|
||||
|
||||
export const AssemblySymmetryParams = {
|
||||
//
|
||||
|
||||
export const AssemblySymmetryDataParams = {
|
||||
serverUrl: PD.Text(AssemblySymmetry.DefaultServerUrl, { description: 'GraphQL endpoint URL' })
|
||||
}
|
||||
export type AssemblySymmetryDataParams = typeof AssemblySymmetryDataParams
|
||||
export type AssemblySymmetryDataProps = PD.Values<AssemblySymmetryDataParams>
|
||||
|
||||
export type AssemblySymmetryDataValue = NonNullableArray<NonNullable<NonNullable<AssemblySymmetryQuery['assembly']>['rcsb_struct_symmetry']>>
|
||||
|
||||
export const AssemblySymmetryDataProvider: CustomStructureProperty.Provider<AssemblySymmetryDataParams, AssemblySymmetryDataValue> = CustomStructureProperty.createProvider({
|
||||
label: 'Assembly Symmetry Data',
|
||||
descriptor: CustomPropertyDescriptor({
|
||||
name: 'rcsb_struct_symmetry_data',
|
||||
// TODO `cifExport` and `symbol`
|
||||
}),
|
||||
type: 'root',
|
||||
defaultParams: AssemblySymmetryDataParams,
|
||||
getParams: (data: Structure) => AssemblySymmetryDataParams,
|
||||
isApplicable: (data: Structure) => AssemblySymmetry.isApplicable(data),
|
||||
obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<AssemblySymmetryDataProps>) => {
|
||||
const p = { ...PD.getDefaultValues(AssemblySymmetryDataParams), ...props }
|
||||
return await AssemblySymmetry.fetch(ctx, data, p)
|
||||
}
|
||||
})
|
||||
|
||||
//
|
||||
|
||||
function getAssemblySymmetryParams(data?: Structure) {
|
||||
return {
|
||||
... AssemblySymmetryDataParams,
|
||||
symmetryIndex: getSymmetrySelectParam(data)
|
||||
}
|
||||
}
|
||||
|
||||
export const AssemblySymmetryParams = getAssemblySymmetryParams()
|
||||
export type AssemblySymmetryParams = typeof AssemblySymmetryParams
|
||||
export type AssemblySymmetryProps = PD.Values<AssemblySymmetryParams>
|
||||
|
||||
export type AssemblySymmetryValue = NonNullableArray<NonNullable<NonNullable<AssemblySymmetryQuery['assembly']>['rcsb_struct_symmetry']>>
|
||||
export type AssemblySymmetryValue = AssemblySymmetryDataValue[0]
|
||||
|
||||
export const AssemblySymmetryProvider: CustomStructureProperty.Provider<AssemblySymmetryParams, AssemblySymmetryValue> = CustomStructureProperty.createProvider({
|
||||
label: 'Assembly Symmetry',
|
||||
@@ -110,10 +143,14 @@ export const AssemblySymmetryProvider: CustomStructureProperty.Provider<Assembly
|
||||
}),
|
||||
type: 'root',
|
||||
defaultParams: AssemblySymmetryParams,
|
||||
getParams: (data: Structure) => AssemblySymmetryParams,
|
||||
getParams: getAssemblySymmetryParams,
|
||||
isApplicable: (data: Structure) => AssemblySymmetry.isApplicable(data),
|
||||
obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<AssemblySymmetryProps>) => {
|
||||
const p = { ...PD.getDefaultValues(AssemblySymmetryParams), ...props }
|
||||
return await AssemblySymmetry.fetch(ctx, data, p)
|
||||
const p = { ...PD.getDefaultValues(getAssemblySymmetryParams(data)), ...props }
|
||||
await AssemblySymmetryDataProvider.attach(ctx, data, p)
|
||||
const assemblySymmetryData = AssemblySymmetryDataProvider.get(data).value
|
||||
const assemblySymmetry = assemblySymmetryData?.[p.symmetryIndex]
|
||||
if (!assemblySymmetry) new Error(`No assembly symmetry found for index ${p.symmetryIndex}`)
|
||||
return assemblySymmetry
|
||||
}
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { AssemblySymmetryValue, getSymmetrySelectParam, AssemblySymmetryProvider, AssemblySymmetry } from '../assembly-symmetry';
|
||||
import { AssemblySymmetryValue, AssemblySymmetryProvider, AssemblySymmetry } from '../assembly-symmetry';
|
||||
import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
|
||||
import { addCylinder } from '../../../mol-geo/geometry/mesh/builder/cylinder';
|
||||
@@ -51,7 +51,6 @@ function axesColorHelp(value: { name: string, params: {} }) {
|
||||
const SharedParams = {
|
||||
...Mesh.Params,
|
||||
scale: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }),
|
||||
symmetryIndex: getSymmetrySelectParam(),
|
||||
}
|
||||
|
||||
const AxesParams = {
|
||||
@@ -87,7 +86,7 @@ export type AssemblySymmetryProps = PD.Values<AssemblySymmetryParams>
|
||||
//
|
||||
|
||||
function getAssemblyName(s: Structure) {
|
||||
const { id } = s.units[0].conformation.operator.assembly
|
||||
const id = s.units[0].conformation.operator.assembly?.id || ''
|
||||
return isInteger(id) ? `Assembly ${id}` : id
|
||||
}
|
||||
|
||||
@@ -113,9 +112,9 @@ const getOrderPrimitive = memoize1((order: number): Primitive | undefined => {
|
||||
})
|
||||
|
||||
function getAxesMesh(data: AssemblySymmetryValue, props: PD.Values<AxesParams>, mesh?: Mesh) {
|
||||
const { symmetryIndex, scale } = props
|
||||
const { scale } = props
|
||||
|
||||
const { rotation_axes } = data[symmetryIndex]
|
||||
const { rotation_axes } = data
|
||||
if (!AssemblySymmetry.isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh)
|
||||
|
||||
const { start, end } = rotation_axes[0]
|
||||
@@ -158,7 +157,7 @@ function getAxesShape(ctx: RuntimeContext, data: Structure, props: AssemblySymme
|
||||
const geo = getAxesMesh(assemblySymmetry, props, shape && shape.geometry);
|
||||
const getColor = (groupId: number) => {
|
||||
if (props.axesColor.name === 'byOrder') {
|
||||
const { rotation_axes } = assemblySymmetry[props.symmetryIndex]
|
||||
const { rotation_axes } = assemblySymmetry
|
||||
const order = rotation_axes![groupId]?.order
|
||||
if (order === 2) return OrderColors[2]
|
||||
else if (order === 3) return OrderColors[3]
|
||||
@@ -168,7 +167,7 @@ function getAxesShape(ctx: RuntimeContext, data: Structure, props: AssemblySymme
|
||||
}
|
||||
}
|
||||
const getLabel = (groupId: number) => {
|
||||
const { type, symbol, kind, rotation_axes } = assemblySymmetry[props.symmetryIndex]
|
||||
const { type, symbol, kind, rotation_axes } = assemblySymmetry
|
||||
const order = rotation_axes![groupId]?.order
|
||||
return [
|
||||
`<small>${data.model.entryId}</small>`,
|
||||
@@ -279,9 +278,9 @@ function setSymbolTransform(t: Mat4, symbol: string, axes: AssemblySymmetry.Rota
|
||||
|
||||
function getCageMesh(data: Structure, props: PD.Values<CageParams>, mesh?: Mesh) {
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(data).value!
|
||||
const { symmetryIndex, scale } = props
|
||||
const { scale } = props
|
||||
|
||||
const { rotation_axes, symbol } = assemblySymmetry[symmetryIndex]
|
||||
const { rotation_axes, symbol } = assemblySymmetry
|
||||
if (!AssemblySymmetry.isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh)
|
||||
|
||||
const cage = getSymbolCage(symbol)
|
||||
@@ -308,7 +307,7 @@ function getCageShape(ctx: RuntimeContext, data: Structure, props: AssemblySymme
|
||||
return props.cageColor
|
||||
}
|
||||
const getLabel = (groupId: number) => {
|
||||
const { type, symbol, kind } = assemblySymmetry[props.symmetryIndex]
|
||||
const { type, symbol, kind } = assemblySymmetry
|
||||
data.model.entryId
|
||||
return [
|
||||
`<small>${data.model.entryId}</small>`,
|
||||
|
||||
@@ -289,6 +289,6 @@ export const ClashesRepresentationProvider = StructureRepresentationProvider({
|
||||
isApplicable: (structure: Structure) => structure.elementCount > 0,
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, structure: Structure) => ClashesProvider.attach(ctx, structure, void 0, true),
|
||||
detach: (_, data) => ClashesProvider.ref(data, false)
|
||||
detach: (data) => ClashesProvider.ref(data, false)
|
||||
}
|
||||
})
|
||||
@@ -7,7 +7,7 @@
|
||||
import { ThemeDataContext } from '../../../mol-theme/theme';
|
||||
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition'
|
||||
import { AssemblySymmetryProvider, AssemblySymmetry, getSymmetrySelectParam } from '../assembly-symmetry';
|
||||
import { AssemblySymmetryProvider, AssemblySymmetry } from '../assembly-symmetry';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { Unit, StructureElement, StructureProperties } from '../../../mol-model/structure';
|
||||
import { Location } from '../../../mol-model/location';
|
||||
@@ -32,13 +32,11 @@ function clusterMemberKey(asymId: string, operList: string[]) {
|
||||
}
|
||||
|
||||
export const AssemblySymmetryClusterColorThemeParams = {
|
||||
...getPaletteParams({ scaleList: 'red-yellow-blue' }),
|
||||
symmetryIndex: getSymmetrySelectParam(),
|
||||
...getPaletteParams({ colorList: 'red-yellow-blue' }),
|
||||
}
|
||||
export type AssemblySymmetryClusterColorThemeParams = typeof AssemblySymmetryClusterColorThemeParams
|
||||
export function getAssemblySymmetryClusterColorThemeParams(ctx: ThemeDataContext) {
|
||||
const params = PD.clone(AssemblySymmetryClusterColorThemeParams)
|
||||
params.symmetryIndex = getSymmetrySelectParam(ctx.structure)
|
||||
return params
|
||||
}
|
||||
|
||||
@@ -46,11 +44,10 @@ export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props:
|
||||
let color: LocationColor = () => DefaultColor
|
||||
let legend: ScaleLegend | TableLegend | undefined
|
||||
|
||||
const { symmetryIndex } = props
|
||||
const assemblySymmetry = ctx.structure && AssemblySymmetryProvider.get(ctx.structure)
|
||||
const contextHash = assemblySymmetry?.version
|
||||
|
||||
const clusters = assemblySymmetry?.value?.[symmetryIndex]?.clusters
|
||||
const clusters = assemblySymmetry?.value?.clusters
|
||||
|
||||
if (clusters?.length && ctx.structure) {
|
||||
const clusterByMember = new Map<string, number>()
|
||||
@@ -69,11 +66,12 @@ export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props:
|
||||
const palette = getPalette(clusters.length, props)
|
||||
legend = palette.legend
|
||||
|
||||
const _emptyList: any[] = [];
|
||||
color = (location: Location): Color => {
|
||||
if (StructureElement.Location.is(location)) {
|
||||
const { assembly } = location.unit.conformation.operator
|
||||
const asymId = getAsymId(location.unit)(location)
|
||||
const cluster = clusterByMember.get(clusterMemberKey(asymId, assembly.operList))
|
||||
const cluster = clusterByMember.get(clusterMemberKey(asymId, assembly?.operList || _emptyList))
|
||||
return cluster !== undefined ? palette.color(cluster) : DefaultColor
|
||||
}
|
||||
return DefaultColor
|
||||
@@ -101,6 +99,6 @@ export const AssemblySymmetryClusterColorThemeProvider: ColorTheme.Provider<Asse
|
||||
isApplicable: (ctx: ThemeDataContext) => AssemblySymmetry.isApplicable(ctx.structure),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? AssemblySymmetryProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
|
||||
detach: (_, data) => data.structure && data.structure.customPropertyDescriptors.reference(AssemblySymmetryProvider.descriptor, false)
|
||||
detach: (data) => data.structure && data.structure.customPropertyDescriptors.reference(AssemblySymmetryProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,6 @@ export const DensityFitColorThemeProvider: ColorTheme.Provider<{}, ValidationRep
|
||||
isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (_, data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
@@ -110,6 +110,6 @@ export const GeometryQualityColorThemeProvider: ColorTheme.Provider<GeometricQua
|
||||
isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (_, data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,6 @@ export const RandomCoilIndexColorThemeProvider: ColorTheme.Provider<{}, Validati
|
||||
isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (_, data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
@@ -202,8 +202,7 @@ function createInterUnitClashes(structure: Structure, clashes: ValidationReport[
|
||||
const builder = new InterUnitGraph.Builder<Unit.Atomic, UnitIndex, InterUnitClashesProps>()
|
||||
const { a, b, edgeProps: { id, magnitude, distance } } = clashes
|
||||
|
||||
const pA = Vec3()
|
||||
const pB = Vec3()
|
||||
const pA = Vec3(), pB = Vec3()
|
||||
|
||||
Structure.eachUnitPair(structure, (unitA: Unit, unitB: Unit) => {
|
||||
const elementsA = unitA.elements
|
||||
@@ -248,6 +247,8 @@ function createIntraUnitClashes(unit: Unit.Atomic, clashes: ValidationReport['cl
|
||||
const magnitudes: number[] = []
|
||||
const distances: number[] = []
|
||||
|
||||
const pA = Vec3(), pB = Vec3()
|
||||
|
||||
const { elements } = unit
|
||||
const { a, b, edgeCount, edgeProps } = clashes
|
||||
|
||||
@@ -257,11 +258,17 @@ function createIntraUnitClashes(unit: Unit.Atomic, clashes: ValidationReport['cl
|
||||
let indexB = SortedArray.indexOf(elements, b[i])
|
||||
|
||||
if (indexA !== -1 && indexB !== -1) {
|
||||
aIndices.push(indexA as UnitIndex)
|
||||
bIndices.push(indexB as UnitIndex)
|
||||
ids.push(edgeProps.id[i])
|
||||
magnitudes.push(edgeProps.magnitude[i])
|
||||
distances.push(edgeProps.distance[i])
|
||||
unit.conformation.position(a[i], pA)
|
||||
unit.conformation.position(b[i], pB)
|
||||
|
||||
// check actual distance to avoid clashes between unrelated chain instances
|
||||
if (equalEps(edgeProps.distance[i], Vec3.distance(pA, pB), 0.1)) {
|
||||
aIndices.push(indexA as UnitIndex)
|
||||
bIndices.push(indexB as UnitIndex)
|
||||
ids.push(edgeProps.id[i])
|
||||
magnitudes.push(edgeProps.magnitude[i])
|
||||
distances.push(edgeProps.distance[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,7 +286,6 @@ function createIntraUnitClashes(unit: Unit.Atomic, clashes: ValidationReport['cl
|
||||
}
|
||||
|
||||
function createClashes(structure: Structure, clashes: ValidationReport['clashes']): Clashes {
|
||||
|
||||
const intraUnit = IntMap.Mutable<IntraUnitClashes>()
|
||||
|
||||
for (let i = 0, il = structure.unitSymmetryGroups.length; i < il; ++i) {
|
||||
|
||||
@@ -71,9 +71,9 @@ namespace Loci {
|
||||
export function getBundleBoundingSphere(bundle: Bundle<any>): Sphere3D {
|
||||
const spheres = bundle.loci.map(l => getBoundingSphere(l)).filter(s => !!s) as Sphere3D[]
|
||||
boundaryHelper.reset();
|
||||
for (const s of spheres) boundaryHelper.includeSphereStep(s.center, s.radius);
|
||||
for (const s of spheres) boundaryHelper.includePositionRadius(s.center, s.radius);
|
||||
boundaryHelper.finishedIncludeStep();
|
||||
for (const s of spheres) boundaryHelper.radiusSphereStep(s.center, s.radius);
|
||||
for (const s of spheres) boundaryHelper.radiusPositionRadius(s.center, s.radius);
|
||||
return boundaryHelper.getSphere();
|
||||
}
|
||||
|
||||
@@ -192,21 +192,6 @@ namespace Loci {
|
||||
? StructureElement.Loci.extendToWholeChains(loci)
|
||||
: loci
|
||||
},
|
||||
'elementInstances': (loci: Loci) => {
|
||||
return StructureElement.Loci.is(loci)
|
||||
? StructureElement.Loci.extendToAllInstances(loci)
|
||||
: loci
|
||||
},
|
||||
'residueInstances': (loci: Loci) => {
|
||||
return StructureElement.Loci.is(loci)
|
||||
? StructureElement.Loci.extendToAllInstances(StructureElement.Loci.extendToWholeResidues(loci, true))
|
||||
: loci
|
||||
},
|
||||
'chainInstances': (loci: Loci) => {
|
||||
return StructureElement.Loci.is(loci)
|
||||
? StructureElement.Loci.extendToAllInstances(StructureElement.Loci.extendToWholeChains(loci))
|
||||
: loci
|
||||
},
|
||||
'entity': (loci: Loci) => {
|
||||
return StructureElement.Loci.is(loci)
|
||||
? StructureElement.Loci.extendToWholeEntities(loci)
|
||||
@@ -224,9 +209,25 @@ namespace Loci {
|
||||
? Shape.Loci(loci.shape)
|
||||
: loci
|
||||
},
|
||||
'elementInstances': (loci: Loci) => {
|
||||
return StructureElement.Loci.is(loci)
|
||||
? StructureElement.Loci.extendToAllInstances(loci)
|
||||
: loci
|
||||
},
|
||||
'residueInstances': (loci: Loci) => {
|
||||
return StructureElement.Loci.is(loci)
|
||||
? StructureElement.Loci.extendToAllInstances(StructureElement.Loci.extendToWholeResidues(loci, true))
|
||||
: loci
|
||||
},
|
||||
'chainInstances': (loci: Loci) => {
|
||||
return StructureElement.Loci.is(loci)
|
||||
? StructureElement.Loci.extendToAllInstances(StructureElement.Loci.extendToWholeChains(loci))
|
||||
: loci
|
||||
},
|
||||
}
|
||||
export type Granularity = keyof typeof Granularity
|
||||
export const GranularityOptions = ParamDefinition.objectToOptions(Granularity, k => {
|
||||
if (k.indexOf('Instances') > 0) return [stringToWords(k), 'With Symmetry'];
|
||||
switch (k) {
|
||||
case 'element': return'Atom/Coarse Element'
|
||||
case 'structure': return'Structure/Shape'
|
||||
|
||||
@@ -69,6 +69,6 @@ class CustomProperties {
|
||||
}
|
||||
|
||||
has(desc: CustomPropertyDescriptor<any>): boolean {
|
||||
return this._refs.has(desc);
|
||||
return this._set.has(desc);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,22 @@ import CifField = CifWriter.Field
|
||||
import CifCategory = CifWriter.Category
|
||||
import E = CifWriter.Encodings
|
||||
|
||||
const _label_asym_id = P.chain.label_asym_id;
|
||||
function atom_site_label_asym_id(e: StructureElement.Location) {
|
||||
const l = _label_asym_id(e);
|
||||
const suffix = e.unit.conformation.operator.suffix;
|
||||
if (!suffix) return l;
|
||||
return l + suffix;
|
||||
}
|
||||
|
||||
const _auth_asym_id = P.chain.auth_asym_id;
|
||||
function atom_site_auth_asym_id(e: StructureElement.Location) {
|
||||
const l = _auth_asym_id(e);
|
||||
const suffix = e.unit.conformation.operator.suffix;
|
||||
if (!suffix) return l;
|
||||
return l + suffix;
|
||||
}
|
||||
|
||||
const atom_site_fields = CifWriter.fields<StructureElement.Location, Structure>()
|
||||
.str('group_PDB', P.residue.group_PDB)
|
||||
.index('id')
|
||||
@@ -29,14 +45,14 @@ const atom_site_fields = CifWriter.fields<StructureElement.Location, Structure>(
|
||||
.str('label_alt_id', P.atom.label_alt_id)
|
||||
.str('pdbx_PDB_ins_code', P.residue.pdbx_PDB_ins_code)
|
||||
|
||||
.str('label_asym_id', P.chain.label_asym_id)
|
||||
.str('label_asym_id', atom_site_label_asym_id)
|
||||
.str('label_entity_id', P.chain.label_entity_id)
|
||||
|
||||
.float('Cartn_x', P.atom.x, { digitCount: 3, encoder: E.fixedPoint3 })
|
||||
.float('Cartn_y', P.atom.y, { digitCount: 3, encoder: E.fixedPoint3 })
|
||||
.float('Cartn_z', P.atom.z, { digitCount: 3, encoder: E.fixedPoint3 })
|
||||
.float('occupancy', P.atom.occupancy, { digitCount: 2, encoder: E.fixedPoint2 })
|
||||
.int('pdbx_formal_charge', P.atom.pdbx_formal_charge, {
|
||||
.int('pdbx_formal_charge', P.atom.pdbx_formal_charge, {
|
||||
encoder: E.deltaRLE,
|
||||
valueKind: (k, d) => k.unit.model.atomicHierarchy.atoms.pdbx_formal_charge.valueKind(k.element)
|
||||
})
|
||||
@@ -44,12 +60,12 @@ const atom_site_fields = CifWriter.fields<StructureElement.Location, Structure>(
|
||||
.str('auth_atom_id', P.atom.auth_atom_id)
|
||||
.str('auth_comp_id', P.residue.auth_comp_id)
|
||||
.int('auth_seq_id', P.residue.auth_seq_id, { encoder: E.deltaRLE })
|
||||
.str('auth_asym_id', P.chain.auth_asym_id)
|
||||
.str('auth_asym_id', atom_site_auth_asym_id)
|
||||
|
||||
.int('pdbx_PDB_model_num', P.unit.model_num, { encoder: E.deltaRLE })
|
||||
.str('operator_name', P.unit.operator_name, {
|
||||
shouldInclude: structure => structure.units.some(u => !u.conformation.operator.isIdentity)
|
||||
})
|
||||
// .str('operator_name', P.unit.operator_name, {
|
||||
// shouldInclude: structure => structure.units.some(u => !u.conformation.operator.isIdentity)
|
||||
// })
|
||||
.getFields();
|
||||
|
||||
export const _atom_site: CifCategory<CifExportContext> = {
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { SymmetryOperator } from '../../../../mol-math/geometry';
|
||||
import { CifExportContext } from '../mmcif';
|
||||
import { StructureElement, StructureProperties as P } from '../../structure';
|
||||
import Unit from '../../structure/unit';
|
||||
import { Segmentation } from '../../../../mol-data/int';
|
||||
import { CifWriter } from '../../../../mol-io/writer/cif';
|
||||
import { Column } from '../../../../mol-data/db';
|
||||
|
||||
export function atom_site_operator_mapping(encoder: CifWriter.Encoder, ctx: CifExportContext) {
|
||||
const entries = getEntries(ctx);
|
||||
if (entries.length === 0) return;
|
||||
encoder.writeCategory(Category, entries, { ignoreFilter: true });
|
||||
}
|
||||
|
||||
export const AtomSiteOperatorMappingSchema = {
|
||||
molstar_atom_site_operator_mapping: {
|
||||
label_asym_id: Column.Schema.Str(),
|
||||
auth_asym_id: Column.Schema.Str(),
|
||||
operator_name: Column.Schema.Str(),
|
||||
suffix: Column.Schema.Str(),
|
||||
|
||||
// assembly
|
||||
assembly_id: Column.Schema.Str(),
|
||||
assembly_operator_id: Column.Schema.Int(),
|
||||
|
||||
// symmetry
|
||||
symmetry_operator_index: Column.Schema.Int(),
|
||||
symmetry_hkl: Column.Schema.Vector(3),
|
||||
|
||||
// NCS
|
||||
ncs_id: Column.Schema.Str(),
|
||||
}
|
||||
}
|
||||
|
||||
const asmValueKind = (i: number, xs: Entry[]) => typeof xs[i].operator.assembly === 'undefined' ? Column.ValueKind.NotPresent : Column.ValueKind.Present;
|
||||
const symmetryValueKind = (i: number, xs: Entry[]) => xs[i].operator.spgrOp === -1 ? Column.ValueKind.NotPresent : Column.ValueKind.Present;
|
||||
|
||||
const Fields = CifWriter.fields<number, Entry[], keyof (typeof AtomSiteOperatorMappingSchema)['molstar_atom_site_operator_mapping']>()
|
||||
.str('label_asym_id', (i, xs) => xs[i].label_asym_id)
|
||||
.str('auth_asym_id', (i, xs) => xs[i].auth_asym_id)
|
||||
.str('operator_name', (i, xs) => xs[i].operator.name)
|
||||
.str('suffix', (i, xs) => xs[i].operator.suffix)
|
||||
// assembly
|
||||
// TODO: include oper list as well?
|
||||
.str('assembly_id', (i, xs) => xs[i].operator.assembly?.id || '', { valueKind: asmValueKind })
|
||||
.int('assembly_operator_id', (i, xs) => xs[i].operator.assembly?.operId || 0, { valueKind: asmValueKind })
|
||||
// symmetry
|
||||
.int('symmetry_operator_index', (i, xs) => xs[i].operator.spgrOp, { valueKind: symmetryValueKind })
|
||||
.vec('symmetry_hkl', [(i, xs) => xs[i].operator.hkl[0], (i, xs) => xs[i].operator.hkl[1], (i, xs) => xs[i].operator.hkl[2]], { valueKind: symmetryValueKind })
|
||||
// NCS
|
||||
.str('ncs_id', (i, xs) => xs[i].operator.ncsId || '', { valueKind: (i, xs) => !xs[i].operator.ncsId ? Column.ValueKind.NotPresent : Column.ValueKind.Present })
|
||||
.getFields()
|
||||
|
||||
const Category: CifWriter.Category<Entry[]> = {
|
||||
name: 'molstar_atom_site_operator_mapping',
|
||||
instance(entries: Entry[]) {
|
||||
return { fields: Fields, source: [{ data: entries, rowCount: entries.length }] };
|
||||
}
|
||||
}
|
||||
|
||||
interface Entry {
|
||||
label_asym_id: string,
|
||||
auth_asym_id: string,
|
||||
operator: SymmetryOperator
|
||||
}
|
||||
|
||||
function getEntries(ctx: CifExportContext) {
|
||||
const existing = new Set<string>();
|
||||
const entries: Entry[] = [];
|
||||
|
||||
for (const s of ctx.structures) {
|
||||
const l = StructureElement.Location.create(s);
|
||||
for (const unit of s.units) {
|
||||
const operator = unit.conformation.operator;
|
||||
if (!operator.suffix || unit.kind !== Unit.Kind.Atomic) continue;
|
||||
|
||||
l.unit = unit;
|
||||
|
||||
const { elements } = unit;
|
||||
const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
|
||||
while (chainsIt.hasNext) {
|
||||
const chainSegment = chainsIt.move();
|
||||
l.element = elements[chainSegment.start];
|
||||
|
||||
const label_asym_id = P.chain.label_asym_id(l);
|
||||
const key = `${label_asym_id}${operator.suffix}`;
|
||||
|
||||
if (existing.has(key)) continue;
|
||||
existing.add(key);
|
||||
|
||||
const auth_asym_id = P.chain.label_asym_id(l);
|
||||
entries.push({ label_asym_id, auth_asym_id, operator });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import { Model } from '../model';
|
||||
import { getUniqueEntityIndicesFromStructures, copy_mmCif_category } from './categories/utils';
|
||||
import { _struct_asym, _entity_poly, _entity_poly_seq } from './categories/sequence';
|
||||
import { CustomPropertyDescriptor } from '../common/custom-property';
|
||||
import { atom_site_operator_mapping } from './categories/atom_site_operator_mapping';
|
||||
|
||||
export interface CifExportContext {
|
||||
structures: Structure[],
|
||||
@@ -136,6 +137,10 @@ export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structures:
|
||||
encoder.writeCategory(cat, ctx);
|
||||
}
|
||||
|
||||
if ((!_params.skipCategoryNames || !_params.skipCategoryNames.has('atom_site')) && encoder.isCategoryIncluded('atom_site')) {
|
||||
atom_site_operator_mapping(encoder, ctx);
|
||||
}
|
||||
|
||||
for (const customProp of models[0].customProperties.all) {
|
||||
encodeCustomProp(customProp, ctx, encoder, _params);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ 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';
|
||||
import { ChainIndex } from './indexing';
|
||||
import { SymmetryOperator } from '../../../mol-math/geometry';
|
||||
|
||||
/**
|
||||
* Interface to the "source data" of the molecule.
|
||||
@@ -60,6 +62,7 @@ export interface Model extends Readonly<{
|
||||
atomicHierarchy: AtomicHierarchy,
|
||||
atomicConformation: AtomicConformation,
|
||||
atomicRanges: AtomicRanges,
|
||||
atomicChainOperatorMappinng: Map<ChainIndex, SymmetryOperator>,
|
||||
|
||||
properties: {
|
||||
/** map that holds details about unobserved or zero occurrence residues */
|
||||
|
||||
@@ -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>
|
||||
@@ -21,9 +21,9 @@ interface Location<U = Unit> {
|
||||
}
|
||||
|
||||
namespace Location {
|
||||
export function create<U extends Unit>(structure: Structure | undefined, unit?: U, element?: ElementIndex): Location<U> {
|
||||
export function create<U extends Unit>(structure?: Structure, unit?: U, element?: ElementIndex): Location<U> {
|
||||
return {
|
||||
kind: 'element-location',
|
||||
kind: 'element-location',
|
||||
structure: structure as any,
|
||||
unit: unit as any,
|
||||
element: element || (0 as ElementIndex)
|
||||
|
||||
@@ -485,7 +485,7 @@ export namespace Loci {
|
||||
for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
|
||||
const eI = elements[OrderedSet.getAt(indices, i)];
|
||||
pos(eI, tempPosBoundary);
|
||||
boundaryHelper.includeSphereStep(tempPosBoundary, r(eI));
|
||||
boundaryHelper.includePositionRadius(tempPosBoundary, r(eI));
|
||||
}
|
||||
}
|
||||
boundaryHelper.finishedIncludeStep();
|
||||
@@ -496,7 +496,7 @@ export namespace Loci {
|
||||
for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
|
||||
const eI = elements[OrderedSet.getAt(indices, i)];
|
||||
pos(eI, tempPosBoundary);
|
||||
boundaryHelper.radiusSphereStep(tempPosBoundary, r(eI));
|
||||
boundaryHelper.radiusPositionRadius(tempPosBoundary, r(eI));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import Unit from './unit'
|
||||
import { VdwRadius } from '../model/properties/atomic';
|
||||
import { SecondaryStructureType } from '../model/types';
|
||||
import { SecondaryStructureProvider } from '../../../mol-model-props/computed/secondary-structure';
|
||||
import { SymmetryOperator } from '../../../mol-math/geometry';
|
||||
|
||||
function p<T>(p: StructureElement.Property<T>) { return p; }
|
||||
|
||||
@@ -167,6 +168,7 @@ const entity = {
|
||||
pdbx_ec: p(l => l.unit.model.entities.data.pdbx_ec.value(eK(l)))
|
||||
}
|
||||
|
||||
const _emptyList: any[] = [];
|
||||
const unit = {
|
||||
id: p(l => l.unit.id),
|
||||
chainGroupId: p(l => l.unit.chainGroupId),
|
||||
@@ -180,8 +182,8 @@ const unit = {
|
||||
spgrOp: p(l => l.unit.conformation.operator.spgrOp),
|
||||
|
||||
model_num: p(l => l.unit.model.modelNum),
|
||||
pdbx_struct_assembly_id: p(l => l.unit.conformation.operator.assembly.id),
|
||||
pdbx_struct_oper_list_ids: p(l => l.unit.conformation.operator.assembly.operList),
|
||||
pdbx_struct_assembly_id: p(l => l.unit.conformation.operator.assembly?.id || SymmetryOperator.DefaultName),
|
||||
pdbx_struct_oper_list_ids: p(l => l.unit.conformation.operator.assembly?.operList || _emptyList),
|
||||
struct_ncs_oper_id: p(l => l.unit.conformation.operator.ncsId),
|
||||
}
|
||||
|
||||
|
||||
@@ -205,7 +205,13 @@ class Structure {
|
||||
}
|
||||
|
||||
get coordinateSystem() {
|
||||
return this._props.coordinateSystem;
|
||||
// TODO: do not use SymmetryOperator for this?
|
||||
// TODO: figure out a good way to compose this
|
||||
return this.parent?.coordinateSystem || this._props.coordinateSystem;
|
||||
}
|
||||
|
||||
set coordinateSystem(op: SymmetryOperator) {
|
||||
this._props.coordinateSystem = op;
|
||||
}
|
||||
|
||||
get label() {
|
||||
@@ -635,11 +641,13 @@ namespace Structure {
|
||||
*/
|
||||
export function ofModel(model: Model): Structure {
|
||||
const chains = model.atomicHierarchy.chainAtomSegments;
|
||||
const { index } = model.atomicHierarchy
|
||||
const { auth_asym_id } = model.atomicHierarchy.chains
|
||||
const { index } = model.atomicHierarchy;
|
||||
const { auth_asym_id } = model.atomicHierarchy.chains;
|
||||
const { atomicChainOperatorMappinng } = model;
|
||||
const builder = new StructureBuilder({ label: model.label });
|
||||
|
||||
for (let c = 0 as ChainIndex; c < chains.count; c++) {
|
||||
const operator = atomicChainOperatorMappinng.get(c) || SymmetryOperator.Default;
|
||||
const start = chains.offsets[c];
|
||||
|
||||
// set to true for chains that consist of "single atom residues",
|
||||
@@ -655,11 +663,15 @@ namespace Structure {
|
||||
singleAtomResidues = true
|
||||
const e1 = index.getEntityFromChain(c);
|
||||
const e2 = index.getEntityFromChain(c + 1 as ChainIndex);
|
||||
if (e1 !== e2) break
|
||||
if (e1 !== e2) break;
|
||||
|
||||
const a1 = auth_asym_id.value(c);
|
||||
const a2 = auth_asym_id.value(c + 1);
|
||||
if (a1 !== a2) break
|
||||
if (a1 !== a2) break;
|
||||
|
||||
const op1 = atomicChainOperatorMappinng.get(c);
|
||||
const op2 = atomicChainOperatorMappinng.get(c + 1 as ChainIndex);
|
||||
if (op1 !== op2) break;
|
||||
|
||||
multiChain = true
|
||||
c++;
|
||||
@@ -668,12 +680,12 @@ namespace Structure {
|
||||
const elements = SortedArray.ofBounds(start as ElementIndex, chains.offsets[c + 1] as ElementIndex);
|
||||
|
||||
if (singleAtomResidues) {
|
||||
partitionAtomicUnitByAtom(model, elements, builder, multiChain);
|
||||
partitionAtomicUnitByAtom(model, elements, builder, multiChain, operator);
|
||||
} else if (elements.length > 200000 || isWaterChain(model, c)) {
|
||||
// split up very large chains e.g. lipid bilayers, micelles or water with explicit H
|
||||
partitionAtomicUnitByResidue(model, elements, builder, multiChain);
|
||||
partitionAtomicUnitByResidue(model, elements, builder, multiChain, operator);
|
||||
} else {
|
||||
builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements, multiChain ? Unit.Trait.MultiChain : Unit.Trait.None);
|
||||
builder.addUnit(Unit.Kind.Atomic, model, operator, elements, multiChain ? Unit.Trait.MultiChain : Unit.Trait.None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,7 +707,7 @@ namespace Structure {
|
||||
return model.entities.data.type.value(e) === 'water';
|
||||
}
|
||||
|
||||
function partitionAtomicUnitByAtom(model: Model, indices: SortedArray, builder: StructureBuilder, multiChain: boolean) {
|
||||
function partitionAtomicUnitByAtom(model: Model, indices: SortedArray, builder: StructureBuilder, multiChain: boolean, operator: SymmetryOperator) {
|
||||
const { x, y, z } = model.atomicConformation;
|
||||
const position = { x, y, z, indices }
|
||||
const lookup = GridLookup3D(position, getBoundary(position), 8192);
|
||||
@@ -710,13 +722,13 @@ namespace Structure {
|
||||
for (let j = 0, _j = count[i]; j < _j; j++) {
|
||||
set[j] = indices[array[start + j]];
|
||||
}
|
||||
builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, SortedArray.ofSortedArray(set), traits);
|
||||
builder.addUnit(Unit.Kind.Atomic, model, operator, SortedArray.ofSortedArray(set), traits);
|
||||
}
|
||||
builder.endChainGroup();
|
||||
}
|
||||
|
||||
// keeps atoms of residues together
|
||||
function partitionAtomicUnitByResidue(model: Model, indices: SortedArray, builder: StructureBuilder, multiChain: boolean) {
|
||||
function partitionAtomicUnitByResidue(model: Model, indices: SortedArray, builder: StructureBuilder, multiChain: boolean, operator: SymmetryOperator) {
|
||||
const { residueAtomSegments } = model.atomicHierarchy
|
||||
|
||||
const startIndices: number[] = []
|
||||
@@ -749,7 +761,7 @@ namespace Structure {
|
||||
set[set.length] = l;
|
||||
}
|
||||
}
|
||||
builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, SortedArray.ofSortedArray(new Int32Array(set)), traits);
|
||||
builder.addUnit(Unit.Kind.Atomic, model, operator, SortedArray.ofSortedArray(new Int32Array(set)), traits);
|
||||
}
|
||||
builder.endChainGroup();
|
||||
}
|
||||
@@ -769,12 +781,12 @@ namespace Structure {
|
||||
const units: Unit[] = [];
|
||||
for (const u of s.units) {
|
||||
const old = u.conformation.operator;
|
||||
const op = SymmetryOperator.create(old.name, transform, old.assembly, old.ncsId, old.hkl);
|
||||
const op = SymmetryOperator.create(old.name, transform, old);
|
||||
units.push(u.applyOperator(u.id, op));
|
||||
}
|
||||
|
||||
const cs = s.coordinateSystem;
|
||||
const newCS = SymmetryOperator.compose(SymmetryOperator.create(cs.name, transform, cs.assembly, cs.ncsId, cs.hkl), cs);
|
||||
const newCS = SymmetryOperator.compose(SymmetryOperator.create(cs.name, transform, cs), cs);
|
||||
return new Structure(units, { parent: s, coordinateSystem: newCS });
|
||||
}
|
||||
|
||||
@@ -1002,14 +1014,17 @@ namespace Structure {
|
||||
|
||||
//
|
||||
|
||||
const DefaultSizeThresholds = {
|
||||
export const DefaultSizeThresholds = {
|
||||
smallResidueCount: 10,
|
||||
mediumResidueCount: 1500,
|
||||
largeResidueCount: 12000,
|
||||
mediumResidueCount: 3000,
|
||||
/** large ribosomes like 4UG0 should still be `large` */
|
||||
largeResidueCount: 20000,
|
||||
highSymmetryUnitCount: 10,
|
||||
fiberResidueCount: 15
|
||||
fiberResidueCount: 15,
|
||||
|
||||
residueCountFactor: 1
|
||||
}
|
||||
type SizeThresholds = typeof DefaultSizeThresholds
|
||||
export type SizeThresholds = typeof DefaultSizeThresholds
|
||||
|
||||
function getPolymerSymmetryGroups(structure: Structure) {
|
||||
return structure.unitSymmetryGroups.filter(ug => ug.units[0].polymerElements.length > 0)
|
||||
@@ -1030,7 +1045,7 @@ namespace Structure {
|
||||
function hasHighSymmetry(structure: Structure, thresholds: SizeThresholds) {
|
||||
const polymerSymmetryGroups = getPolymerSymmetryGroups(structure)
|
||||
return (
|
||||
polymerSymmetryGroups.length > 1 &&
|
||||
polymerSymmetryGroups.length >= 1 &&
|
||||
polymerSymmetryGroups[0].units.length > thresholds.highSymmetryUnitCount
|
||||
)
|
||||
}
|
||||
@@ -1038,8 +1053,8 @@ namespace Structure {
|
||||
export enum Size { Small, Medium, Large, Huge, Gigantic }
|
||||
|
||||
export function getSize(structure: Structure, thresholds: Partial<SizeThresholds> = {}): Size {
|
||||
const t = { ...DefaultSizeThresholds, thresholds }
|
||||
if (structure.polymerResidueCount >= t.largeResidueCount) {
|
||||
const t = { ...DefaultSizeThresholds, ...thresholds }
|
||||
if (structure.polymerResidueCount >= t.largeResidueCount * t.residueCountFactor) {
|
||||
if (hasHighSymmetry(structure, t)) {
|
||||
return Size.Huge
|
||||
} else {
|
||||
@@ -1047,9 +1062,9 @@ namespace Structure {
|
||||
}
|
||||
} else if (isFiberLike(structure, t)) {
|
||||
return Size.Small
|
||||
} else if (structure.polymerResidueCount < t.smallResidueCount) {
|
||||
} else if (structure.polymerResidueCount < t.smallResidueCount * t.residueCountFactor) {
|
||||
return Size.Small
|
||||
} else if (structure.polymerResidueCount < t.mediumResidueCount) {
|
||||
} else if (structure.polymerResidueCount < t.mediumResidueCount * t.residueCountFactor) {
|
||||
return Size.Medium
|
||||
} else {
|
||||
return Size.Large
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace StructureSymmetry {
|
||||
const assembly = Symmetry.findAssembly(models[0], asmName);
|
||||
if (!assembly) throw new Error(`Assembly '${asmName}' is not defined.`);
|
||||
|
||||
const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { id: assembly.id, operList: [] })
|
||||
const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { assembly: { id: assembly.id, operId: 0, operList: [] } })
|
||||
const assembler = Structure.Builder({ coordinateSystem, label: structure.label });
|
||||
|
||||
const queryCtx = new QueryContext(structure);
|
||||
@@ -57,7 +57,7 @@ namespace StructureSymmetry {
|
||||
if (models.length !== 1) throw new Error('Can only build symmetry assemblies from structures based on 1 model.');
|
||||
|
||||
const modelCenter = Vec3()
|
||||
const assembler = Structure.Builder({ label: structure.label });
|
||||
const assembler = Structure.Builder({ label: structure.label, representativeModel: models[0] });
|
||||
|
||||
const queryCtx = new QueryContext(structure);
|
||||
|
||||
@@ -150,7 +150,12 @@ function getOperatorsForIndex(symmetry: Symmetry, index: number, i: number, j: n
|
||||
for (let u = 0, ul = ncsOperators.length; u < ul; ++u) {
|
||||
const ncsOp = ncsOperators![u]
|
||||
const matrix = Mat4.mul(Mat4(), symOp.matrix, ncsOp.matrix)
|
||||
const operator = SymmetryOperator.create(`${symOp.name} ${ncsOp.name}`, matrix, symOp.assembly, ncsOp.ncsId, symOp.hkl, symOp.spgrOp);
|
||||
const operator = SymmetryOperator.create(`${symOp.name} ${ncsOp.name}`, matrix, {
|
||||
assembly: symOp.assembly,
|
||||
ncsId: ncsOp.ncsId,
|
||||
hkl: symOp.hkl,
|
||||
spgrOp: symOp.spgrOp
|
||||
});
|
||||
operators.push(operator)
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -34,14 +34,14 @@ export function computeStructureBoundary(s: Structure): Boundary {
|
||||
Vec3.min(min, min, invariantBoundary.box.min);
|
||||
Vec3.max(max, max, invariantBoundary.box.max);
|
||||
|
||||
boundaryHelper.includeSphereStep(invariantBoundary.sphere.center, invariantBoundary.sphere.radius);
|
||||
boundaryHelper.includePositionRadius(invariantBoundary.sphere.center, invariantBoundary.sphere.radius);
|
||||
} else {
|
||||
Box3D.transform(tmpBox, invariantBoundary.box, o.matrix);
|
||||
Vec3.min(min, min, tmpBox.min);
|
||||
Vec3.max(max, max, tmpBox.max);
|
||||
|
||||
Sphere3D.transform(tmpSphere, invariantBoundary.sphere, o.matrix);
|
||||
boundaryHelper.includeSphereStep(tmpSphere.center, tmpSphere.radius);
|
||||
boundaryHelper.includePositionRadius(tmpSphere.center, tmpSphere.radius);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,10 +53,10 @@ export function computeStructureBoundary(s: Structure): Boundary {
|
||||
const o = u.conformation.operator;
|
||||
|
||||
if (o.isIdentity) {
|
||||
boundaryHelper.radiusSphereStep(invariantBoundary.sphere.center, invariantBoundary.sphere.radius);
|
||||
boundaryHelper.radiusPositionRadius(invariantBoundary.sphere.center, invariantBoundary.sphere.radius);
|
||||
} else {
|
||||
Sphere3D.transform(tmpSphere, invariantBoundary.sphere, o.matrix);
|
||||
boundaryHelper.radiusSphereStep(tmpSphere.center, tmpSphere.radius);
|
||||
boundaryHelper.radiusPositionRadius(tmpSphere.center, tmpSphere.radius);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,11 +6,10 @@
|
||||
*/
|
||||
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { StateAction, StateBuilder, StateSelection, StateTransformer } from '../../mol-state';
|
||||
import { StateAction, StateSelection, StateTransformer } from '../../mol-state';
|
||||
import { Task } from '../../mol-task';
|
||||
import { FileInfo } from '../../mol-util/file-info';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { TrajectoryFormat } from '../builder/structure';
|
||||
import { BuiltInTrajectoryFormat } from '../formats/trajectory';
|
||||
import { RootStructureDefinition } from '../helpers/root-structure';
|
||||
import { PluginStateObject } from '../objects';
|
||||
@@ -18,8 +17,7 @@ import { StateTransforms } from '../transforms';
|
||||
import { Download, ParsePsf } from '../transforms/data';
|
||||
import { CoordinatesFromDcd, CustomModelProperties, CustomStructureProperties, TopologyFromPsf, TrajectoryFromModelAndCoordinates } from '../transforms/model';
|
||||
import { DataFormatProvider, guessCifVariant } from './data-format';
|
||||
import { applyTrajectoryHierarchyPreset } from '../builder/structure/hierarchy-preset';
|
||||
import { PresetStructureReprentations } from '../builder/structure/representation-preset';
|
||||
import { PresetStructureRepresentations } from '../builder/structure/representation-preset';
|
||||
|
||||
// TODO make unitcell creation part of preset
|
||||
|
||||
@@ -38,7 +36,7 @@ export const MmcifProvider: DataFormatProvider<PluginStateObject.Data.String | P
|
||||
return Task.create('mmCIF default builder', async () => {
|
||||
const trajectory = await ctx.builders.structure.parseTrajectory(data, 'mmcif');
|
||||
const representationPreset = options.visuals ? 'auto' : 'empty';
|
||||
await applyTrajectoryHierarchyPreset(ctx, trajectory, 'first-model', { showUnitcell: options.visuals, representationPreset });
|
||||
await ctx.builders.structure.hierarchy.applyPreset(trajectory, 'default', { showUnitcell: options.visuals, representationPreset });
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -55,7 +53,7 @@ export const PdbProvider: DataFormatProvider<any> = {
|
||||
return Task.create('PDB default builder', async () => {
|
||||
const trajectory = await ctx.builders.structure.parseTrajectory(data, 'pdb');
|
||||
const representationPreset = options.visuals ? 'auto' : 'empty';
|
||||
await applyTrajectoryHierarchyPreset(ctx, trajectory, 'first-model', { showUnitcell: options.visuals, representationPreset });
|
||||
await ctx.builders.structure.hierarchy.applyPreset(trajectory, 'default', { showUnitcell: options.visuals, representationPreset });
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -72,7 +70,7 @@ export const GroProvider: DataFormatProvider<any> = {
|
||||
return Task.create('GRO default builder', async () => {
|
||||
const trajectory = await ctx.builders.structure.parseTrajectory(data, 'gro');
|
||||
const representationPreset = options.visuals ? 'auto' : 'empty';
|
||||
await applyTrajectoryHierarchyPreset(ctx, trajectory, 'first-model', { showUnitcell: options.visuals, representationPreset });
|
||||
await ctx.builders.structure.hierarchy.applyPreset(trajectory, 'default', { showUnitcell: options.visuals, representationPreset });
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -89,7 +87,7 @@ export const Provider3dg: DataFormatProvider<any> = {
|
||||
return Task.create('3DG default builder', async () => {
|
||||
const trajectory = await ctx.builders.structure.parseTrajectory(data, '3dg');
|
||||
const representationPreset = options.visuals ? 'auto' : 'empty';
|
||||
await applyTrajectoryHierarchyPreset(ctx, trajectory, 'first-model', { showUnitcell: options.visuals, representationPreset });
|
||||
await ctx.builders.structure.hierarchy.applyPreset(trajectory, 'default', { showUnitcell: options.visuals, representationPreset });
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -130,8 +128,8 @@ export const DcdProvider: DataFormatProvider<any> = {
|
||||
|
||||
const DownloadModelRepresentationOptions = (plugin: PluginContext) => PD.Group({
|
||||
type: RootStructureDefinition.getParams(void 0, 'auto').type,
|
||||
representation: PD.Select(PresetStructureReprentations.auto.id,
|
||||
plugin.builders.structure.representation.getPresets().map(p => [p.id, p.display.name] as any),
|
||||
representation: PD.Select(PresetStructureRepresentations.auto.id,
|
||||
plugin.builders.structure.representation.getPresets().map(p => [p.id, p.display.name, p.display.group] as any),
|
||||
{ description: 'Which representation preset to use.' }),
|
||||
asTrajectory: PD.Optional(PD.Boolean(false, { description: 'Load all entries into a single trajectory.' }))
|
||||
}, { isExpanded: false });
|
||||
@@ -225,8 +223,8 @@ const DownloadStructure = StateAction.build({
|
||||
default: throw new Error(`${(src as any).name} not supported.`);
|
||||
}
|
||||
|
||||
const representationPreset: any = params.source.params.options.representation || PresetStructureReprentations.auto.id;
|
||||
const showUnitcell = representationPreset !== PresetStructureReprentations.empty.id;
|
||||
const representationPreset: any = params.source.params.options.representation || PresetStructureRepresentations.auto.id;
|
||||
const showUnitcell = representationPreset !== PresetStructureRepresentations.empty.id;
|
||||
|
||||
const structure = src.params.options.type.name === 'auto' ? void 0 : src.params.options.type;
|
||||
|
||||
@@ -238,7 +236,7 @@ const DownloadStructure = StateAction.build({
|
||||
}, { state: { isGhost: true } });
|
||||
const trajectory = await plugin.builders.structure.parseTrajectory(blob, { formats: downloadParams.map((_, i) => ({ id: '' + i, format: 'cif' as 'cif' })) });
|
||||
|
||||
await applyTrajectoryHierarchyPreset(plugin, trajectory, 'first-model', {
|
||||
await plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default', {
|
||||
structure,
|
||||
showUnitcell,
|
||||
representationPreset
|
||||
@@ -248,7 +246,7 @@ const DownloadStructure = StateAction.build({
|
||||
const data = await plugin.builders.data.download(download, { state: { isGhost: true } });
|
||||
const trajectory = await plugin.builders.structure.parseTrajectory(data, format);
|
||||
|
||||
await applyTrajectoryHierarchyPreset(plugin, trajectory, 'first-model', {
|
||||
await plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default', {
|
||||
structure,
|
||||
showUnitcell,
|
||||
representationPreset
|
||||
@@ -267,42 +265,6 @@ function getDownloadParams(src: string, url: (id: string) => string, label: (id:
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function createModelTree(b: StateBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, format: TrajectoryFormat = 'cif') {
|
||||
let parsed: StateBuilder.To<PluginStateObject.Molecule.Trajectory>
|
||||
switch (format) {
|
||||
case 'cif':
|
||||
parsed = b.apply(StateTransforms.Data.ParseCif, void 0, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.TrajectoryFromMmCif)
|
||||
break
|
||||
case 'pdb':
|
||||
parsed = b.apply(StateTransforms.Model.TrajectoryFromPDB);
|
||||
break
|
||||
case 'gro':
|
||||
parsed = b.apply(StateTransforms.Model.TrajectoryFromGRO);
|
||||
break
|
||||
case '3dg':
|
||||
parsed = b.apply(StateTransforms.Model.TrajectoryFrom3DG);
|
||||
break
|
||||
default:
|
||||
throw new Error('unsupported format')
|
||||
}
|
||||
|
||||
return parsed.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 });
|
||||
}
|
||||
|
||||
export const Create3DRepresentationPreset = StateAction.build({
|
||||
display: { name: '3D Representation Preset', description: 'Create one of preset 3D representations.' },
|
||||
from: PluginStateObject.Molecule.Structure,
|
||||
isApplicable(a, _, plugin: PluginContext) { return plugin.builders.structure.representation.hasPreset(a); },
|
||||
params(a, plugin: PluginContext) {
|
||||
return {
|
||||
type: plugin.builders.structure.representation.getPresetsWithOptions(a)
|
||||
};
|
||||
}
|
||||
})(({ ref, params }, plugin: PluginContext) => {
|
||||
return plugin.builders.structure.representation.applyPreset(ref, params.type.name, params.type.params);
|
||||
});
|
||||
|
||||
export const UpdateTrajectory = StateAction.build({
|
||||
display: { name: 'Update Trajectory' },
|
||||
params: {
|
||||
@@ -356,15 +318,6 @@ export const EnableStructureCustomProps = StateAction.build({
|
||||
}
|
||||
})(({ ref, params }, ctx: PluginContext) => ctx.builders.structure.insertStructureProperties(ref, params));
|
||||
|
||||
export const TransformStructureConformation = StateAction.build({
|
||||
display: { name: 'Transform Conformation' },
|
||||
from: PluginStateObject.Molecule.Structure,
|
||||
params: StateTransforms.Model.TransformStructureConformation.definition.params,
|
||||
})(({ ref, params, state }) => {
|
||||
const root = state.build().to(ref).insert(StateTransforms.Model.TransformStructureConformation, params as any);
|
||||
return state.updateTree(root);
|
||||
});
|
||||
|
||||
export const AddTrajectory = StateAction.build({
|
||||
display: { name: 'Add Trajectory', description: 'Add trajectory from existing model/topology and coordinates.' },
|
||||
from: PluginStateObject.Root,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { PluginComponent } from '../component';
|
||||
import { StatefulPluginComponent } from '../component';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginStateAnimation } from './model';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
@@ -15,7 +15,7 @@ export { PluginAnimationManager }
|
||||
// TODO: handle unregistered animations on state restore
|
||||
// TODO: better API
|
||||
|
||||
class PluginAnimationManager extends PluginComponent<PluginAnimationManager.State> {
|
||||
class PluginAnimationManager extends StatefulPluginComponent<PluginAnimationManager.State> {
|
||||
private map = new Map<string, PluginStateAnimation>();
|
||||
private animations: PluginStateAnimation[] = [];
|
||||
private _current: PluginAnimationManager.Current;
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
|
||||
export interface PresetProvider<O extends StateObject = StateObject, P = any, S = {}> {
|
||||
id: string,
|
||||
display: { name: string, group: string, description?: string },
|
||||
display: { name: string, group?: string, description?: string },
|
||||
isApplicable?(a: O, plugin: PluginContext): boolean,
|
||||
params?(a: O | undefined, plugin: PluginContext): PD.For<P>,
|
||||
apply(a: StateObjectRef<O>, params: P, plugin: PluginContext): Promise<S> | S
|
||||
|
||||
@@ -18,19 +18,7 @@ import { StructureElement } from '../../mol-model/structure';
|
||||
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
|
||||
import { SpacegroupCell } from '../../mol-math/geometry';
|
||||
import Expression from '../../mol-script/language/expression';
|
||||
|
||||
export type TrajectoryFormat = 'pdb' | 'cif' | 'gro' | '3dg'
|
||||
|
||||
export enum StructureBuilderTags {
|
||||
Trajectory = 'trajectory',
|
||||
Model = 'model',
|
||||
ModelUnitcell = 'model-unitcell',
|
||||
ModelProperties = 'model-properties',
|
||||
ModelGenericRepresentation = 'model-generic-representation',
|
||||
Structure = 'structure',
|
||||
StructureProperties = 'structure-properties',
|
||||
Component = 'structure-component'
|
||||
}
|
||||
import { TrajectoryHierarchyBuilder } from './structure/hierarchy';
|
||||
|
||||
export class StructureBuilder {
|
||||
private get dataState() {
|
||||
@@ -40,7 +28,7 @@ export class StructureBuilder {
|
||||
private async parseTrajectoryData(data: StateObjectRef<SO.Data.Binary | SO.Data.String>, format: BuiltInTrajectoryFormat | TrajectoryFormatProvider) {
|
||||
const provider = typeof format === 'string' ? this.plugin.dataFormat.trajectory.get(format) : format;
|
||||
if (!provider) throw new Error(`'${format}' is not a supported data format.`);
|
||||
const { trajectory } = await provider.parse(this.plugin, data, { trajectoryTags: StructureBuilderTags.Trajectory });
|
||||
const { trajectory } = await provider.parse(this.plugin, data);
|
||||
return trajectory;
|
||||
}
|
||||
|
||||
@@ -48,46 +36,14 @@ 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);
|
||||
await this.plugin.updateDataState(trajectory, { revertOnError: true });
|
||||
return trajectory.selector;
|
||||
}
|
||||
|
||||
readonly hierarchy = new TrajectoryHierarchyBuilder(this.plugin);
|
||||
readonly representation = new StructureRepresentationBuilder(this.plugin);
|
||||
|
||||
// async parseStructure(params: {
|
||||
// data?: StateObjectRef<SO.Data.Binary | SO.Data.String>,
|
||||
// dataFormat?: BuiltInTrajectoryFormat | TrajectoryFormatProvider,
|
||||
// blob?: StateObjectRef<SO.Data.Blob>
|
||||
// blobParams?: StateTransformer.Params<StateTransforms['Data']['ParseBlob']>,
|
||||
// model?: StateTransformer.Params<StateTransforms['Model']['ModelFromTrajectory']>,
|
||||
// modelProperties?: boolean | StateTransformer.Params<StateTransforms['Model']['CustomModelProperties']>,
|
||||
// structure?: RootStructureDefinition.Params,
|
||||
// structureProperties?: boolean | StateTransformer.Params<StateTransforms['Model']['CustomStructureProperties']>
|
||||
// }) {
|
||||
// const trajectory = params.data
|
||||
// ? await this.parseTrajectory(params.data, params.dataFormat! || 'cif')
|
||||
// : await this.parseTrajectoryBlob(params.blob!, params.blobParams!);
|
||||
|
||||
// const model = await this.createModel(trajectory, params.model);
|
||||
// const modelProperties = !!params.modelProperties
|
||||
// ? await this.insertModelProperties(model, typeof params?.modelProperties !== 'boolean' ? params?.modelProperties : void 0) : void 0;
|
||||
|
||||
// const structure = await this.createStructure(modelProperties || model, params.structure);
|
||||
// const structureProperties = !!params.structureProperties
|
||||
// ? await this.insertStructureProperties(structure, typeof params?.structureProperties !== 'boolean' ? params?.structureProperties : void 0) : void 0;
|
||||
|
||||
// return {
|
||||
// trajectory,
|
||||
// model: modelProperties || model,
|
||||
// modelRoot: model,
|
||||
// modelProperties,
|
||||
// structure: structureProperties || structure,
|
||||
// structureRoot: structure,
|
||||
// structureProperties
|
||||
// };
|
||||
// }
|
||||
|
||||
async parseTrajectory(data: StateObjectRef<SO.Data.Binary | SO.Data.String>, format: BuiltInTrajectoryFormat | TrajectoryFormatProvider): Promise<StateObjectSelector<SO.Molecule.Trajectory>>
|
||||
async parseTrajectory(blob: StateObjectRef<SO.Data.Blob>, params: StateTransformer.Params<StateTransforms['Data']['ParseBlob']>): Promise<StateObjectSelector<SO.Molecule.Trajectory>>
|
||||
async parseTrajectory(data: StateObjectRef, params: any) {
|
||||
@@ -104,16 +60,16 @@ export class StructureBuilder {
|
||||
async createModel(trajectory: StateObjectRef<SO.Molecule.Trajectory>, params?: StateTransformer.Params<StateTransforms['Model']['ModelFromTrajectory']>, initialState?: Partial<StateTransform.State>) {
|
||||
const state = this.dataState;
|
||||
const model = state.build().to(trajectory)
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, params || { modelIndex: 0 }, { tags: StructureBuilderTags.Model, state: initialState });
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, params || { modelIndex: 0 }, { state: initialState });
|
||||
|
||||
await this.plugin.updateDataState(model, { revertOnError: true });
|
||||
return model.selector;
|
||||
}
|
||||
|
||||
async insertModelProperties(model: StateObjectRef<SO.Molecule.Model>, params?: StateTransformer.Params<StateTransforms['Model']['CustomModelProperties']>) {
|
||||
async insertModelProperties(model: StateObjectRef<SO.Molecule.Model>, params?: StateTransformer.Params<StateTransforms['Model']['CustomModelProperties']>, initialState?: Partial<StateTransform.State>) {
|
||||
const state = this.dataState;
|
||||
const props = state.build().to(model)
|
||||
.insert(StateTransforms.Model.CustomModelProperties, params, { tags: StructureBuilderTags.ModelProperties, isDecorator: true });
|
||||
.apply(StateTransforms.Model.CustomModelProperties, params, { state: initialState });
|
||||
await this.plugin.updateDataState(props, { revertOnError: true });
|
||||
return props.selector;
|
||||
}
|
||||
@@ -126,7 +82,7 @@ export class StructureBuilder {
|
||||
if (SpacegroupCell.isZero(cell)) return;
|
||||
|
||||
const unitcell = state.build().to(model)
|
||||
.apply(StateTransforms.Representation.ModelUnitcell3D, params, { tags: StructureBuilderTags.ModelUnitcell, state: initialState });
|
||||
.apply(StateTransforms.Representation.ModelUnitcell3D, params, { state: initialState });
|
||||
await this.plugin.updateDataState(unitcell, { revertOnError: true });
|
||||
return unitcell.selector;
|
||||
}
|
||||
@@ -143,7 +99,7 @@ export class StructureBuilder {
|
||||
}
|
||||
|
||||
const structure = state.build().to(modelRef)
|
||||
.apply(StateTransforms.Model.StructureFromModel, { type: params || { name: 'assembly', params: { } } }, { tags: StructureBuilderTags.Structure, state: initialState });
|
||||
.apply(StateTransforms.Model.StructureFromModel, { type: params || { name: 'assembly', params: { } } }, { state: initialState });
|
||||
|
||||
await this.plugin.updateDataState(structure, { revertOnError: true });
|
||||
return structure.selector;
|
||||
@@ -152,7 +108,7 @@ export class StructureBuilder {
|
||||
async insertStructureProperties(structure: StateObjectRef<SO.Molecule.Structure>, params?: StateTransformer.Params<StateTransforms['Model']['CustomStructureProperties']>) {
|
||||
const state = this.dataState;
|
||||
const props = state.build().to(structure)
|
||||
.insert(StateTransforms.Model.CustomStructureProperties, params, { tags: StructureBuilderTags.StructureProperties, isDecorator: true });
|
||||
.apply(StateTransforms.Model.CustomStructureProperties, params);
|
||||
await this.plugin.updateDataState(props, { revertOnError: true });
|
||||
return props.selector;
|
||||
}
|
||||
@@ -162,14 +118,14 @@ export class StructureBuilder {
|
||||
}
|
||||
|
||||
/** returns undefined if the component is empty/null */
|
||||
async tryCreateComponent(structure: StateObjectRef<SO.Molecule.Structure>, params: StructureComponentParams, key: string, tags?: string[]): Promise<StateObjectRef<SO.Molecule.Structure> | undefined> {
|
||||
async tryCreateComponent(structure: StateObjectRef<SO.Molecule.Structure>, params: StructureComponentParams, key: string, tags?: string[]): Promise<StateObjectSelector<SO.Molecule.Structure> | undefined> {
|
||||
const state = this.dataState;
|
||||
|
||||
const root = state.build().to(structure);
|
||||
|
||||
const keyTag = `structure-component-${key}`;
|
||||
const component = root.applyOrUpdateTagged(keyTag, StateTransforms.Model.StructureComponent, params, {
|
||||
tags: tags ? [...tags, StructureBuilderTags.Component, keyTag] : [StructureBuilderTags.Component, keyTag]
|
||||
tags: tags ? [...tags, keyTag] : [keyTag]
|
||||
});
|
||||
|
||||
await this.plugin.updateDataState(component);
|
||||
@@ -201,7 +157,7 @@ export class StructureBuilder {
|
||||
}, key, params?.tags);
|
||||
}
|
||||
|
||||
tryCreateComponentFromSelection(structure: StateObjectRef<SO.Molecule.Structure>, selection: StructureSelectionQuery, key: string, params?: { label?: string, tags?: string[] }): Promise<StateObjectRef<SO.Molecule.Structure> | undefined> {
|
||||
tryCreateComponentFromSelection(structure: StateObjectRef<SO.Molecule.Structure>, selection: StructureSelectionQuery, key: string, params?: { label?: string, tags?: string[] }): Promise<StateObjectSelector<SO.Molecule.Structure> | undefined> {
|
||||
return this.plugin.runTask(Task.create('Query Component', async taskCtx => {
|
||||
let { label, tags } = params || { };
|
||||
label = (label || '').trim();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* 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 { PresetProvider } from '../preset-provider';
|
||||
@@ -10,35 +11,41 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { StateObjectRef, StateTransformer } from '../../../mol-state';
|
||||
import { StateTransforms } from '../../transforms';
|
||||
import { RootStructureDefinition } from '../../helpers/root-structure';
|
||||
import { PresetStructureReprentations } from './representation-preset';
|
||||
import { PresetStructureRepresentations } from './representation-preset';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { isProductionMode } from '../../../mol-util/debug';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { Model } from '../../../mol-model/structure';
|
||||
import { getStructureQuality } from '../../../mol-repr/util';
|
||||
|
||||
export interface TrajectoryHierarchyPresetProvider<P = any, S = {}> extends PresetProvider<PluginStateObject.Molecule.Trajectory, P, S> { }
|
||||
export function TrajectoryHierarchyPresetProvider<P, S>(preset: TrajectoryHierarchyPresetProvider<P, S>) { return preset; }
|
||||
export namespace TrajectoryHierarchyPresetProvider {
|
||||
export type Params<P extends TrajectoryHierarchyPresetProvider> = P extends TrajectoryHierarchyPresetProvider<infer T> ? T : never;
|
||||
export type State<P extends TrajectoryHierarchyPresetProvider> = P extends TrajectoryHierarchyPresetProvider<infer _, infer S> ? S : never;
|
||||
|
||||
export const CommonParams = (a: PluginStateObject.Molecule.Trajectory | undefined, plugin: PluginContext) => ({
|
||||
modelProperties: PD.Optional(PD.Group(StateTransformer.getParamDefinition(StateTransforms.Model.CustomModelProperties, void 0, plugin))),
|
||||
structureProperties: PD.Optional(PD.Group(StateTransformer.getParamDefinition(StateTransforms.Model.CustomStructureProperties, void 0, plugin))),
|
||||
representationPreset: PD.Optional(PD.Text<keyof PresetStructureRepresentations>('auto' as const)),
|
||||
})
|
||||
}
|
||||
export function TrajectoryHierarchyPresetProvider<P, S>(preset: TrajectoryHierarchyPresetProvider<P, S>) { return preset; }
|
||||
|
||||
const CommonParams = (a: PluginStateObject.Molecule.Trajectory | undefined, plugin: PluginContext) => ({
|
||||
modelProperties: PD.Optional(PD.Group(StateTransformer.getParamDefinition(StateTransforms.Model.CustomModelProperties, void 0, plugin))),
|
||||
structureProperties: PD.Optional(PD.Group(StateTransformer.getParamDefinition(StateTransforms.Model.CustomStructureProperties, void 0, plugin))),
|
||||
representationPreset: PD.Optional(PD.Text<keyof PresetStructureReprentations>('auto' as const)),
|
||||
})
|
||||
const CommonParams = TrajectoryHierarchyPresetProvider.CommonParams
|
||||
|
||||
const FirstModelParams = (a: PluginStateObject.Molecule.Trajectory | undefined, plugin: PluginContext) => ({
|
||||
const DefaultParams = (a: PluginStateObject.Molecule.Trajectory | undefined, plugin: PluginContext) => ({
|
||||
model: PD.Optional(PD.Group(StateTransformer.getParamDefinition(StateTransforms.Model.ModelFromTrajectory, a, plugin))),
|
||||
showUnitcell: PD.Optional(PD.Boolean(true)),
|
||||
showUnitcell: PD.Optional(PD.Boolean(false)),
|
||||
structure: PD.Optional(RootStructureDefinition.getParams(void 0, 'assembly').type),
|
||||
...CommonParams(a, plugin)
|
||||
});
|
||||
|
||||
const firstModel = TrajectoryHierarchyPresetProvider({
|
||||
id: 'preset-trajectory-first-model',
|
||||
display: { name: 'First Model', group: 'Preset' },
|
||||
params: FirstModelParams,
|
||||
const defaultPreset = TrajectoryHierarchyPresetProvider({
|
||||
id: 'preset-trajectory-default',
|
||||
display: { name: 'Default (Assembly)', group: 'Preset' },
|
||||
isApplicable: o => {
|
||||
return true
|
||||
},
|
||||
params: DefaultParams,
|
||||
async apply(trajectory, params, plugin) {
|
||||
const builder = plugin.builders.structure;
|
||||
|
||||
@@ -49,15 +56,13 @@ const firstModel = TrajectoryHierarchyPresetProvider({
|
||||
const structureProperties = await builder.insertStructureProperties(structure, params.structureProperties);
|
||||
|
||||
const unitcell = params.showUnitcell === void 0 || !!params.showUnitcell ? await builder.tryCreateUnitcell(modelProperties, undefined, { isHidden: true }) : void 0;
|
||||
const representation = await plugin.builders.structure.representation.applyPreset(structureProperties, params.representationPreset || 'auto');
|
||||
const representation = await plugin.builders.structure.representation.applyPreset(structureProperties, params.representationPreset || 'auto');
|
||||
|
||||
return {
|
||||
model: modelProperties,
|
||||
modelRoot: model,
|
||||
model,
|
||||
modelProperties,
|
||||
unitcell,
|
||||
structure: structureProperties,
|
||||
structureRoot: structure,
|
||||
structure,
|
||||
structureProperties,
|
||||
representation
|
||||
};
|
||||
@@ -67,6 +72,9 @@ const firstModel = TrajectoryHierarchyPresetProvider({
|
||||
const allModels = TrajectoryHierarchyPresetProvider({
|
||||
id: 'preset-trajectory-all-models',
|
||||
display: { name: 'All Models', group: 'Preset' },
|
||||
isApplicable: o => {
|
||||
return o.data.length > 1
|
||||
},
|
||||
params: CommonParams,
|
||||
async apply(trajectory, params, plugin) {
|
||||
const tr = StateObjectRef.resolveAndCheck(plugin.state.data, trajectory)?.obj?.data;
|
||||
@@ -77,41 +85,80 @@ const allModels = TrajectoryHierarchyPresetProvider({
|
||||
const models = [], structures = [];
|
||||
|
||||
for (let i = 0; i < tr.length; i++) {
|
||||
const model = await builder.createModel(trajectory, { modelIndex: i }, { isCollapsed: true });
|
||||
const modelProperties = await builder.insertModelProperties(model, params.modelProperties);
|
||||
const model = await builder.createModel(trajectory, { modelIndex: i });
|
||||
const modelProperties = await builder.insertModelProperties(model, params.modelProperties, { isCollapsed: true });
|
||||
const structure = await builder.createStructure(modelProperties || model, { name: 'deposited', params: {} });
|
||||
const structureProperties = await builder.insertStructureProperties(structure, params.structureProperties);
|
||||
|
||||
models.push(model);
|
||||
structures.push(structure);
|
||||
await builder.representation.applyPreset(structureProperties, params.representationPreset || 'auto', { globalThemeName: 'model-index' });
|
||||
|
||||
const quality = structure.obj ? getStructureQuality(structure.obj.data, { elementCountFactor: tr.length }) : 'medium'
|
||||
await builder.representation.applyPreset(structureProperties, params.representationPreset || 'auto', { globalThemeName: 'model-index', quality });
|
||||
}
|
||||
|
||||
return { models, structures };
|
||||
}
|
||||
});
|
||||
|
||||
export const PresetStructureTrajectoryHierarchy = {
|
||||
'first-model': firstModel,
|
||||
'all-models': allModels
|
||||
};
|
||||
export type PresetStructureTrajectoryHierarchy = typeof PresetStructureTrajectoryHierarchy;
|
||||
const CrystalSymmetryParams = (a: PluginStateObject.Molecule.Trajectory | undefined, plugin: PluginContext) => ({
|
||||
model: PD.Optional(PD.Group(StateTransformer.getParamDefinition(StateTransforms.Model.ModelFromTrajectory, a, plugin))),
|
||||
...CommonParams(a, plugin)
|
||||
});
|
||||
|
||||
// TODO: should there be a registry like for representations?
|
||||
async function applyCrystalSymmetry(props: { ijkMin: Vec3, ijkMax: Vec3, theme?: string }, trajectory: StateObjectRef<PluginStateObject.Molecule.Trajectory>, params: PD.ValuesFor<ReturnType<typeof CrystalSymmetryParams>>, plugin: PluginContext) {
|
||||
const builder = plugin.builders.structure;
|
||||
|
||||
export function applyTrajectoryHierarchyPreset<K extends keyof PresetStructureTrajectoryHierarchy>(plugin: PluginContext, parent: StateObjectRef<PluginStateObject.Molecule.Trajectory>, preset: K, params?: Partial<TrajectoryHierarchyPresetProvider.Params<PresetStructureTrajectoryHierarchy[K]>>): Promise<TrajectoryHierarchyPresetProvider.State<PresetStructureTrajectoryHierarchy[K]>> | undefined
|
||||
export function applyTrajectoryHierarchyPreset<P = any, S = {}>(plugin: PluginContext, parent: StateObjectRef<PluginStateObject.Molecule.Trajectory>, provider: TrajectoryHierarchyPresetProvider<P, S>, params?: P): Promise<S> | undefined
|
||||
export function applyTrajectoryHierarchyPreset(plugin: PluginContext, parent: StateObjectRef, providerRef: string | TrajectoryHierarchyPresetProvider, params?: any): Promise<any> | undefined {
|
||||
const provider = typeof providerRef === 'string' ? (PresetStructureTrajectoryHierarchy as any)[providerRef] : providerRef;
|
||||
if (!provider) return;
|
||||
const model = await builder.createModel(trajectory, params.model);
|
||||
const modelProperties = await builder.insertModelProperties(model, params.modelProperties);
|
||||
|
||||
const state = plugin.state.data;
|
||||
const cell = StateObjectRef.resolveAndCheck(state, parent);
|
||||
if (!cell) {
|
||||
if (!isProductionMode) console.warn(`Applying hierarchy preset provider to bad cell.`);
|
||||
return;
|
||||
const structure = await builder.createStructure(modelProperties || model, {
|
||||
name: 'symmetry',
|
||||
params: props
|
||||
});
|
||||
const structureProperties = await builder.insertStructureProperties(structure, params.structureProperties);
|
||||
|
||||
const unitcell = await builder.tryCreateUnitcell(modelProperties, undefined, { isHidden: false });
|
||||
const representation = await plugin.builders.structure.representation.applyPreset(structureProperties, params.representationPreset || 'auto', { globalThemeName: props.theme });
|
||||
|
||||
return {
|
||||
model,
|
||||
modelProperties,
|
||||
unitcell,
|
||||
structure,
|
||||
structureProperties,
|
||||
representation
|
||||
};
|
||||
}
|
||||
|
||||
const unitcell = TrajectoryHierarchyPresetProvider({
|
||||
id: 'preset-trajectory-unitcell',
|
||||
display: { name: 'Unitcell', group: 'Preset' },
|
||||
isApplicable: o => {
|
||||
return Model.hasCrystalSymmetry(o.data[0])
|
||||
},
|
||||
params: CrystalSymmetryParams,
|
||||
async apply(trajectory, params, plugin) {
|
||||
return await applyCrystalSymmetry({ ijkMin: Vec3.create(0, 0, 0), ijkMax: Vec3.create(0, 0, 0) }, trajectory, params, plugin);
|
||||
}
|
||||
const prms = { ...PD.getDefaultValues(provider.params(cell.obj!, plugin) as PD.Params), ...params };
|
||||
const task = Task.create(`${provider.display.name}`, () => provider.apply(cell, prms, plugin) as Promise<any>);
|
||||
return plugin.runTask(task);
|
||||
}
|
||||
});
|
||||
|
||||
const supercell = TrajectoryHierarchyPresetProvider({
|
||||
id: 'preset-trajectory-supercell',
|
||||
display: { name: 'Supercell', group: 'Preset' },
|
||||
isApplicable: o => {
|
||||
return Model.hasCrystalSymmetry(o.data[0])
|
||||
},
|
||||
params: CrystalSymmetryParams,
|
||||
async apply(trajectory, params, plugin) {
|
||||
return await applyCrystalSymmetry({ ijkMin: Vec3.create(-1, -1, -1), ijkMax: Vec3.create(1, 1, 1), theme: 'operator-hkl' }, trajectory, params, plugin);
|
||||
}
|
||||
});
|
||||
|
||||
export const PresetTrajectoryHierarchy = {
|
||||
'default': defaultPreset,
|
||||
'all-models': allModels,
|
||||
unitcell,
|
||||
supercell,
|
||||
};
|
||||
export type PresetTrajectoryHierarchy = typeof PresetTrajectoryHierarchy;
|
||||
113
src/mol-plugin-state/builder/structure/hierarchy.ts
Normal file
113
src/mol-plugin-state/builder/structure/hierarchy.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* 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 { arrayFind } from '../../../mol-data/util';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { StateObjectRef } from '../../../mol-state';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { isProductionMode } from '../../../mol-util/debug';
|
||||
import { objectForEach } from '../../../mol-util/object';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { PluginStateObject } from '../../objects';
|
||||
import { PresetTrajectoryHierarchy, TrajectoryHierarchyPresetProvider } from './hierarchy-preset';
|
||||
import { arrayRemoveInPlace } from '../../../mol-util/array';
|
||||
|
||||
// TODO factor out code shared with StructureRepresentationBuilder?
|
||||
|
||||
export type TrajectoryHierarchyPresetProviderRef = keyof PresetTrajectoryHierarchy | TrajectoryHierarchyPresetProvider | string
|
||||
|
||||
export class TrajectoryHierarchyBuilder {
|
||||
private _providers: TrajectoryHierarchyPresetProvider[] = [];
|
||||
private providerMap: Map<string, TrajectoryHierarchyPresetProvider> = new Map();
|
||||
|
||||
readonly defaultProvider = PresetTrajectoryHierarchy.default;
|
||||
|
||||
private resolveProvider(ref: TrajectoryHierarchyPresetProviderRef) {
|
||||
return typeof ref === 'string'
|
||||
? PresetTrajectoryHierarchy[ref as keyof PresetTrajectoryHierarchy] ?? arrayFind(this._providers, p => p.id === ref)
|
||||
: ref;
|
||||
}
|
||||
|
||||
hasPreset(t: PluginStateObject.Molecule.Trajectory) {
|
||||
for (const p of this._providers) {
|
||||
if (!p.isApplicable || p.isApplicable(t, this.plugin)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
get providers(): ReadonlyArray<TrajectoryHierarchyPresetProvider> { return this._providers; }
|
||||
|
||||
getPresets(t?: PluginStateObject.Molecule.Trajectory) {
|
||||
if (!t) return this.providers;
|
||||
const ret = [];
|
||||
for (const p of this._providers) {
|
||||
if (p.isApplicable && !p.isApplicable(t, this.plugin)) continue;
|
||||
ret.push(p);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
getPresetSelect(t?: PluginStateObject.Molecule.Trajectory): PD.Select<string> {
|
||||
const options: [string, string][] = [];
|
||||
for (const p of this._providers) {
|
||||
if (t && p.isApplicable && !p.isApplicable(t, this.plugin)) continue;
|
||||
options.push([p.id, p.display.name]);
|
||||
}
|
||||
return PD.Select('auto', options);
|
||||
}
|
||||
|
||||
getPresetsWithOptions(t: PluginStateObject.Molecule.Trajectory) {
|
||||
const options: [string, string][] = [];
|
||||
const map: { [K in string]: PD.Any } = Object.create(null);
|
||||
for (const p of this._providers) {
|
||||
if (p.isApplicable && !p.isApplicable(t, this.plugin)) continue;
|
||||
|
||||
options.push([p.id, p.display.name]);
|
||||
map[p.id] = p.params ? PD.Group(p.params(t, this.plugin)) : PD.EmptyGroup()
|
||||
}
|
||||
if (options.length === 0) return PD.MappedStatic('', { '': PD.EmptyGroup() });
|
||||
return PD.MappedStatic(options[0][0], map, { options });
|
||||
}
|
||||
|
||||
registerPreset(provider: TrajectoryHierarchyPresetProvider) {
|
||||
if (this.providerMap.has(provider.id)) {
|
||||
throw new Error(`Hierarchy provider with id '${provider.id}' already registered.`);
|
||||
}
|
||||
this._providers.push(provider);
|
||||
this.providerMap.set(provider.id, provider);
|
||||
}
|
||||
|
||||
unregisterPreset(provider: TrajectoryHierarchyPresetProvider) {
|
||||
this.providerMap.delete(provider.id)
|
||||
arrayRemoveInPlace(this._providers, provider)
|
||||
}
|
||||
|
||||
applyPreset<K extends keyof PresetTrajectoryHierarchy>(parent: StateObjectRef<PluginStateObject.Molecule.Trajectory>, preset: K, params?: Partial<TrajectoryHierarchyPresetProvider.Params<PresetTrajectoryHierarchy[K]>>): Promise<TrajectoryHierarchyPresetProvider.State<PresetTrajectoryHierarchy[K]>> | undefined
|
||||
applyPreset<P = any, S = {}>(parent: StateObjectRef<PluginStateObject.Molecule.Trajectory>, provider: TrajectoryHierarchyPresetProvider<P, S>, params?: P): Promise<S> | undefined
|
||||
applyPreset(parent: StateObjectRef, providerRef: string | TrajectoryHierarchyPresetProvider, params?: any): Promise<any> | undefined {
|
||||
const provider = this.resolveProvider(providerRef);
|
||||
if (!provider) return;
|
||||
|
||||
const state = this.plugin.state.data;
|
||||
const cell = StateObjectRef.resolveAndCheck(state, parent);
|
||||
if (!cell) {
|
||||
if (!isProductionMode) console.warn(`Applying hierarchy preset provider to bad cell.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const prms = params || (provider.params
|
||||
? PD.getDefaultValues(provider.params(cell.obj, this.plugin) as PD.Params)
|
||||
: {})
|
||||
|
||||
const task = Task.create(`${provider.display.name}`, () => provider.apply(cell, prms, this.plugin) as Promise<any>);
|
||||
return this.plugin.runTask(task);
|
||||
}
|
||||
|
||||
constructor(public plugin: PluginContext) {
|
||||
objectForEach(PresetTrajectoryHierarchy, r => this.registerPreset(r));
|
||||
}
|
||||
}
|
||||
@@ -12,28 +12,52 @@ import { VisualQuality, VisualQualityOptions } from '../../../mol-geo/geometry/b
|
||||
import { ColorTheme } from '../../../mol-theme/color';
|
||||
import { Structure } from '../../../mol-model/structure';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { StateObjectRef } from '../../../mol-state';
|
||||
import { StateObjectRef, StateObjectSelector } from '../../../mol-state';
|
||||
import { StaticStructureComponentType } from '../../helpers/structure-component';
|
||||
import { StructureSelectionQueries as Q } from '../../helpers/structure-selection-query';
|
||||
|
||||
export interface StructureRepresentationPresetProvider<P = any, S = {}> extends PresetProvider<PluginStateObject.Molecule.Structure, P, S> { }
|
||||
export interface StructureRepresentationPresetProvider<P = any, S extends _Result = _Result> extends PresetProvider<PluginStateObject.Molecule.Structure, P, S> { }
|
||||
export function StructureRepresentationPresetProvider<P, S extends _Result>(repr: StructureRepresentationPresetProvider<P, S>) { return repr; }
|
||||
export namespace StructureRepresentationPresetProvider {
|
||||
export type Params<P extends StructureRepresentationPresetProvider> = P extends StructureRepresentationPresetProvider<infer T> ? T : never;
|
||||
export type State<P extends StructureRepresentationPresetProvider> = P extends StructureRepresentationPresetProvider<infer _, infer S> ? S : never;
|
||||
}
|
||||
export function StructureRepresentationPresetProvider<P, S>(repr: StructureRepresentationPresetProvider<P, S>) { return repr; }
|
||||
export type Result = {
|
||||
components?: { [name: string]: StateObjectSelector | undefined },
|
||||
representations?: { [name: string]: StateObjectSelector | undefined }
|
||||
}
|
||||
|
||||
export const CommonStructureRepresentationPresetParams = {
|
||||
ignoreHydrogens: PD.Optional(PD.Boolean(false)),
|
||||
quality: PD.Optional(PD.Select<VisualQuality>('auto', VisualQualityOptions)),
|
||||
globalThemeName: PD.Optional(PD.Text<ColorTheme.BuiltIn>(''))
|
||||
export const CommonParams = {
|
||||
ignoreHydrogens: PD.Optional(PD.Boolean(false)),
|
||||
quality: PD.Optional(PD.Select<VisualQuality>('auto', VisualQualityOptions)),
|
||||
globalThemeName: PD.Optional(PD.Text<ColorTheme.BuiltIn>(''))
|
||||
}
|
||||
export type CommonParams = PD.ValuesFor<typeof CommonParams>
|
||||
|
||||
export function reprBuilder(plugin: PluginContext, params: CommonParams) {
|
||||
const update = plugin.state.data.build();
|
||||
const builder = plugin.builders.structure.representation;
|
||||
const typeParams = {
|
||||
quality: plugin.managers.structure.component.state.options.visualQuality,
|
||||
ignoreHydrogens: !plugin.managers.structure.component.state.options.showHydrogens,
|
||||
};
|
||||
if (params.quality && params.quality !== 'auto') typeParams.quality = params.quality;
|
||||
if (params.ignoreHydrogens !== void 0) typeParams.ignoreHydrogens = !!params.ignoreHydrogens;
|
||||
const color: ColorTheme.BuiltIn | undefined = params.globalThemeName ? params.globalThemeName : void 0;
|
||||
|
||||
return { update, builder, color, typeParams };
|
||||
}
|
||||
}
|
||||
export type CommonStructureRepresentationParams = PD.ValuesFor<typeof CommonStructureRepresentationPresetParams>
|
||||
|
||||
type _Result = StructureRepresentationPresetProvider.Result
|
||||
|
||||
const CommonParams = StructureRepresentationPresetProvider.CommonParams
|
||||
type CommonParams = StructureRepresentationPresetProvider.CommonParams
|
||||
const reprBuilder = StructureRepresentationPresetProvider.reprBuilder
|
||||
|
||||
const auto = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-auto',
|
||||
display: { name: 'Automatic', group: 'Preset' },
|
||||
params: () => CommonStructureRepresentationPresetParams,
|
||||
display: { name: 'Automatic' },
|
||||
params: () => CommonParams,
|
||||
apply(ref, params, plugin) {
|
||||
const structure = StateObjectRef.resolveAndCheck(plugin.state.data, ref)?.obj?.data;
|
||||
if (!structure) return { };
|
||||
@@ -53,32 +77,20 @@ const auto = StructureRepresentationPresetProvider({
|
||||
}
|
||||
});
|
||||
|
||||
function reprBuilder(plugin: PluginContext, params: CommonStructureRepresentationParams) {
|
||||
const update = plugin.state.data.build();
|
||||
const builder = plugin.builders.structure.representation;
|
||||
const typeParams = {
|
||||
quality: plugin.managers.structure.component.state.options.visualQuality,
|
||||
ignoreHydrogens: !plugin.managers.structure.component.state.options.showHydrogens,
|
||||
};
|
||||
if (params.quality && params.quality !== 'auto') typeParams.quality = params.quality;
|
||||
if (params.ignoreHydrogens !== void 0) typeParams.ignoreHydrogens = !!params.ignoreHydrogens;
|
||||
const color: ColorTheme.BuiltIn | undefined = params.globalThemeName ? params.globalThemeName : void 0;
|
||||
|
||||
return { update, builder, color, typeParams };
|
||||
}
|
||||
|
||||
const empty = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-empty',
|
||||
display: { name: 'Empty', group: 'Preset' },
|
||||
display: { name: 'Empty' },
|
||||
async apply(ref, params, plugin) {
|
||||
return { };
|
||||
return { };
|
||||
}
|
||||
});
|
||||
|
||||
const BuiltInPresetGroupName = 'Basic'
|
||||
|
||||
const polymerAndLigand = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-polymer-and-ligand',
|
||||
display: { name: 'Polymer & Ligand', group: 'Preset' },
|
||||
params: () => CommonStructureRepresentationPresetParams,
|
||||
display: { name: 'Polymer & Ligand', group: BuiltInPresetGroupName },
|
||||
params: () => CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
if (!structureCell) return {};
|
||||
@@ -94,15 +106,13 @@ const polymerAndLigand = StructureRepresentationPresetProvider({
|
||||
|
||||
const { update, builder, typeParams, color } = reprBuilder(plugin, params);
|
||||
const representations = {
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams, color }),
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams, color }),
|
||||
nonStandard: builder.buildRepresentation(update, components.nonStandard, { type: 'ball-and-stick', typeParams, color: color || 'polymer-id' }),
|
||||
branched: components.branched && {
|
||||
ballAndStick: builder.buildRepresentation(update, components.branched, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.15 }, color }),
|
||||
snfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams, color }),
|
||||
},
|
||||
water: builder.buildRepresentation(update, components.water, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.51 }, color }),
|
||||
coarse: builder.buildRepresentation(update, components.coarse, { type: 'spacefill', typeParams, color: color || 'polymer-id' })
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams, color }, { tag: 'polymer' }),
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams, color }, { tag: 'ligand' }),
|
||||
nonStandard: builder.buildRepresentation(update, components.nonStandard, { type: 'ball-and-stick', typeParams, color: color || 'polymer-id' }, { tag: 'non-standard' }),
|
||||
branchedBallAndStick: builder.buildRepresentation(update, components.branched, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.15 }, color }, { tag: 'branched-ball-and-stick' }),
|
||||
branchedSnfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams, color }, { tag: 'branched-snfg-3d' }),
|
||||
water: builder.buildRepresentation(update, components.water, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.51 }, color }, { tag: 'water' }),
|
||||
coarse: builder.buildRepresentation(update, components.coarse, { type: 'spacefill', typeParams, color: color || 'polymer-id' }, { tag: 'coarse' })
|
||||
};
|
||||
|
||||
await plugin.updateDataState(update, { revertOnError: false });
|
||||
@@ -112,8 +122,8 @@ const polymerAndLigand = StructureRepresentationPresetProvider({
|
||||
|
||||
const proteinAndNucleic = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-protein-and-nucleic',
|
||||
display: { name: 'Protein & Nucleic', group: 'Preset' },
|
||||
params: () => CommonStructureRepresentationPresetParams,
|
||||
display: { name: 'Protein & Nucleic', group: BuiltInPresetGroupName },
|
||||
params: () => CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
if (!structureCell) return {};
|
||||
@@ -125,8 +135,8 @@ const proteinAndNucleic = StructureRepresentationPresetProvider({
|
||||
|
||||
const { update, builder, typeParams, color } = reprBuilder(plugin, params);
|
||||
const representations = {
|
||||
protein: builder.buildRepresentation(update, components.protein, { type: 'cartoon', typeParams, color }),
|
||||
nucleic: builder.buildRepresentation(update, components.nucleic, { type: 'gaussian-surface', typeParams, color })
|
||||
protein: builder.buildRepresentation(update, components.protein, { type: 'cartoon', typeParams, color }, { tag: 'protein' }),
|
||||
nucleic: builder.buildRepresentation(update, components.nucleic, { type: 'gaussian-surface', typeParams, color }, { tag: 'nucleic' })
|
||||
};
|
||||
|
||||
await plugin.updateDataState(update, { revertOnError: true });
|
||||
@@ -136,8 +146,8 @@ const proteinAndNucleic = StructureRepresentationPresetProvider({
|
||||
|
||||
const coarseSurface = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-coarse-surface',
|
||||
display: { name: 'Coarse Surface', group: 'Preset' },
|
||||
params: () => CommonStructureRepresentationPresetParams,
|
||||
display: { name: 'Coarse Surface', group: BuiltInPresetGroupName },
|
||||
params: () => CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
if (!structureCell) return {};
|
||||
@@ -148,26 +158,29 @@ const coarseSurface = StructureRepresentationPresetProvider({
|
||||
const gaussianProps = Object.create(null);
|
||||
const components = Object.create(null);
|
||||
|
||||
let selectionType = 'polymer'
|
||||
|
||||
if (size === Structure.Size.Gigantic) {
|
||||
Object.assign(gaussianProps, {
|
||||
radiusOffset: 1,
|
||||
smoothness: 0.5,
|
||||
visuals: ['structure-gaussian-surface-mesh']
|
||||
})
|
||||
selectionType = 'trace'
|
||||
components.trace = await presetSelectionComponent(plugin, structureCell, 'trace')
|
||||
} else if(size === Structure.Size.Huge) {
|
||||
Object.assign(gaussianProps, {
|
||||
smoothness: 0.5,
|
||||
})
|
||||
components.trace = await presetSelectionComponent(plugin, structureCell, 'polymer')
|
||||
components.trace = await presetStaticComponent(plugin, structureCell, 'polymer')
|
||||
} else {
|
||||
components.trace = await presetSelectionComponent(plugin, structureCell, 'polymer')
|
||||
components.trace = await presetStaticComponent(plugin, structureCell, 'polymer')
|
||||
}
|
||||
|
||||
|
||||
const { update, builder, typeParams, color } = reprBuilder(plugin, params);
|
||||
const representations = {
|
||||
trace: builder.buildRepresentation(update, components.trace, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color })
|
||||
trace: builder.buildRepresentation(update, components.trace, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color }, { tag: selectionType })
|
||||
};
|
||||
|
||||
await plugin.updateDataState(update, { revertOnError: true });
|
||||
@@ -177,19 +190,19 @@ const coarseSurface = StructureRepresentationPresetProvider({
|
||||
|
||||
const polymerCartoon = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-polymer-cartoon',
|
||||
display: { name: 'Polymer Cartoon', group: 'Preset' },
|
||||
params: () => CommonStructureRepresentationPresetParams,
|
||||
display: { name: 'Polymer Cartoon', group: BuiltInPresetGroupName },
|
||||
params: () => CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
if (!structureCell) return {};
|
||||
|
||||
const components = {
|
||||
polymer: await presetSelectionComponent(plugin, structureCell, 'polymer'),
|
||||
polymer: await presetStaticComponent(plugin, structureCell, 'polymer'),
|
||||
};
|
||||
|
||||
const { update, builder, typeParams, color } = reprBuilder(plugin, params);
|
||||
const representations = {
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams, color })
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams, color }, { tag: 'polymer' })
|
||||
};
|
||||
|
||||
await plugin.updateDataState(update, { revertOnError: true });
|
||||
@@ -199,19 +212,19 @@ const polymerCartoon = StructureRepresentationPresetProvider({
|
||||
|
||||
const atomicDetail = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-atomic-detail',
|
||||
display: { name: 'Atomic Detail', group: 'Preset' },
|
||||
params: () => CommonStructureRepresentationPresetParams,
|
||||
display: { name: 'Atomic Detail', group: BuiltInPresetGroupName },
|
||||
params: () => CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
if (!structureCell) return {};
|
||||
|
||||
const components = {
|
||||
all: await presetSelectionComponent(plugin, structureCell, 'all'),
|
||||
all: await presetStaticComponent(plugin, structureCell, 'all'),
|
||||
};
|
||||
|
||||
const { update, builder, typeParams, color } = reprBuilder(plugin, params);
|
||||
const representations = {
|
||||
all: builder.buildRepresentation(update, components.all, { type: 'ball-and-stick', typeParams, color })
|
||||
all: builder.buildRepresentation(update, components.all, { type: 'ball-and-stick', typeParams, color }, { tag: 'all' })
|
||||
};
|
||||
|
||||
await plugin.updateDataState(update, { revertOnError: true });
|
||||
@@ -227,7 +240,7 @@ export function presetSelectionComponent(plugin: PluginContext, structure: State
|
||||
return plugin.builders.structure.tryCreateComponentFromSelection(structure, Q[query], `selection-${query}`);
|
||||
}
|
||||
|
||||
export const PresetStructureReprentations = {
|
||||
export const PresetStructureRepresentations = {
|
||||
empty,
|
||||
auto,
|
||||
'atomic-detail': atomicDetail,
|
||||
@@ -236,4 +249,4 @@ export const PresetStructureReprentations = {
|
||||
'protein-and-nucleic': proteinAndNucleic,
|
||||
'coarse-surface': coarseSurface
|
||||
};
|
||||
export type PresetStructureReprentations = typeof PresetStructureReprentations;
|
||||
export type PresetStructureRepresentations = typeof PresetStructureRepresentations;
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { arrayFind } from '../../../mol-data/util';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { StateBuilder, StateObjectRef, StateObjectSelector } from '../../../mol-state';
|
||||
import { StateBuilder, StateObjectRef, StateObjectSelector, StateTransform } from '../../../mol-state';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { isProductionMode } from '../../../mol-util/debug';
|
||||
import { objectForEach } from '../../../mol-util/object';
|
||||
@@ -14,24 +14,23 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { createStructureRepresentationParams, StructureRepresentationBuiltInProps, StructureRepresentationProps } from '../../helpers/structure-representation-params';
|
||||
import { PluginStateObject } from '../../objects';
|
||||
import { StructureRepresentation3D } from '../../transforms/representation';
|
||||
import { PresetStructureReprentations, StructureRepresentationPresetProvider } from './representation-preset';
|
||||
import { PresetStructureRepresentations, StructureRepresentationPresetProvider } from './representation-preset';
|
||||
import { arrayRemoveInPlace } from '../../../mol-util/array';
|
||||
|
||||
export type StructureRepresentationPresetProviderRef = keyof PresetStructureReprentations | StructureRepresentationPresetProvider | string
|
||||
// TODO factor out code shared with TrajectoryHierarchyBuilder?
|
||||
|
||||
export const enum StructureRepresentationBuilderTags {
|
||||
Representation = 'structure-representation'
|
||||
}
|
||||
export type StructureRepresentationPresetProviderRef = keyof PresetStructureRepresentations | StructureRepresentationPresetProvider | string
|
||||
|
||||
export class StructureRepresentationBuilder {
|
||||
private _providers: StructureRepresentationPresetProvider[] = [];
|
||||
private providerMap: Map<string, StructureRepresentationPresetProvider> = new Map();
|
||||
private get dataState() { return this.plugin.state.data; }
|
||||
|
||||
readonly defaultProvider = PresetStructureReprentations.auto;
|
||||
readonly defaultProvider = PresetStructureRepresentations.auto;
|
||||
|
||||
private resolveProvider(ref: StructureRepresentationPresetProviderRef) {
|
||||
return typeof ref === 'string'
|
||||
? PresetStructureReprentations[ref as keyof PresetStructureReprentations] ?? arrayFind(this._providers, p => p.id === ref)
|
||||
? PresetStructureRepresentations[ref as keyof PresetStructureRepresentations] ?? arrayFind(this._providers, p => p.id === ref)
|
||||
: ref;
|
||||
}
|
||||
|
||||
@@ -55,10 +54,10 @@ export class StructureRepresentationBuilder {
|
||||
}
|
||||
|
||||
getPresetSelect(s?: PluginStateObject.Molecule.Structure): PD.Select<string> {
|
||||
const options: [string, string][] = [];
|
||||
const options: [string, string, string | undefined][] = [];
|
||||
for (const p of this._providers) {
|
||||
if (s && p.isApplicable && !p.isApplicable(s, this.plugin)) continue;
|
||||
options.push([p.id, p.display.name]);
|
||||
options.push([p.id, p.display.name, p.display.group]);
|
||||
}
|
||||
return PD.Select('auto', options);
|
||||
}
|
||||
@@ -78,13 +77,18 @@ export class StructureRepresentationBuilder {
|
||||
|
||||
registerPreset(provider: StructureRepresentationPresetProvider) {
|
||||
if (this.providerMap.has(provider.id)) {
|
||||
throw new Error(`Repr. provider with id '${provider.id}' already registered.`);
|
||||
throw new Error(`Representation provider with id '${provider.id}' already registered.`);
|
||||
}
|
||||
this._providers.push(provider);
|
||||
this.providerMap.set(provider.id, provider);
|
||||
}
|
||||
|
||||
applyPreset<K extends keyof PresetStructureReprentations>(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, preset: K, params?: StructureRepresentationPresetProvider.Params<PresetStructureReprentations[K]>): Promise<StructureRepresentationPresetProvider.State<PresetStructureReprentations[K]>> | undefined
|
||||
unregisterPreset(provider: StructureRepresentationPresetProvider) {
|
||||
this.providerMap.delete(provider.id)
|
||||
arrayRemoveInPlace(this._providers, provider)
|
||||
}
|
||||
|
||||
applyPreset<K extends keyof PresetStructureRepresentations>(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, preset: K, params?: StructureRepresentationPresetProvider.Params<PresetStructureRepresentations[K]>): Promise<StructureRepresentationPresetProvider.State<PresetStructureRepresentations[K]>> | undefined
|
||||
applyPreset<P = any, S = {}>(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, provider: StructureRepresentationPresetProvider<P, S>, params?: P): Promise<S> | undefined
|
||||
applyPreset(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, providerId: string, params?: any): Promise<any> | undefined
|
||||
applyPreset(parent: StateObjectRef, providerRef: string | StructureRepresentationPresetProvider, params?: any): Promise<any> | undefined {
|
||||
@@ -102,41 +106,42 @@ export class StructureRepresentationBuilder {
|
||||
? PD.getDefaultValues(provider.params(cell.obj, this.plugin) as PD.Params)
|
||||
: {})
|
||||
|
||||
|
||||
const task = Task.create(`${provider.display.name}`, () => provider.apply(cell, prms, this.plugin) as Promise<any>);
|
||||
return this.plugin.runTask(task);
|
||||
}
|
||||
|
||||
async addRepresentation<P extends StructureRepresentationBuiltInProps>(structure: StateObjectRef<PluginStateObject.Molecule.Structure>, props?: P): Promise<StateObjectSelector<PluginStateObject.Molecule.Structure.Representation3D>>
|
||||
async addRepresentation<P extends StructureRepresentationProps>(structure: StateObjectRef<PluginStateObject.Molecule.Structure>, props?: P): Promise<StateObjectSelector<PluginStateObject.Molecule.Structure.Representation3D>>
|
||||
async addRepresentation(structure: StateObjectRef<PluginStateObject.Molecule.Structure>, props?: any) {
|
||||
const data = StateObjectRef.resolveAndCheck(this.dataState, structure)?.obj?.data;
|
||||
if (!data) return;
|
||||
|
||||
const params = createStructureRepresentationParams(this.plugin, data, props);
|
||||
const repr = this.dataState.build()
|
||||
.to(structure)
|
||||
.apply(StructureRepresentation3D, params, { tags: StructureRepresentationBuilderTags.Representation });
|
||||
async addRepresentation<P extends StructureRepresentationBuiltInProps>(structure: StateObjectRef<PluginStateObject.Molecule.Structure>, props: P, options?: Partial<StructureRepresentationBuilder.AddRepresentationOptions>): Promise<StateObjectSelector<PluginStateObject.Molecule.Structure.Representation3D>>
|
||||
async addRepresentation<P extends StructureRepresentationProps>(structure: StateObjectRef<PluginStateObject.Molecule.Structure>, props: P, options?: Partial<StructureRepresentationBuilder.AddRepresentationOptions>): Promise<StateObjectSelector<PluginStateObject.Molecule.Structure.Representation3D>>
|
||||
async addRepresentation(structure: StateObjectRef<PluginStateObject.Molecule.Structure>, props: any, options?: Partial<StructureRepresentationBuilder.AddRepresentationOptions>) {
|
||||
const repr = this.dataState.build();
|
||||
const selector = this.buildRepresentation(repr, structure, props, options);
|
||||
if (!selector) return;
|
||||
|
||||
await this.plugin.updateDataState(repr);
|
||||
return repr.selector;
|
||||
return selector;
|
||||
}
|
||||
|
||||
async buildRepresentation<P extends StructureRepresentationBuiltInProps>(builder: StateBuilder.Root, structure: StateObjectRef<PluginStateObject.Molecule.Structure> | undefined, props?: P): Promise<StateObjectSelector<PluginStateObject.Molecule.Structure.Representation3D>>
|
||||
async buildRepresentation<P extends StructureRepresentationProps>(builder: StateBuilder.Root, structure: StateObjectRef<PluginStateObject.Molecule.Structure> | undefined, props?: P): Promise<StateObjectSelector<PluginStateObject.Molecule.Structure.Representation3D>>
|
||||
async buildRepresentation(builder: StateBuilder.Root, structure: StateObjectRef<PluginStateObject.Molecule.Structure> | undefined, props?: any) {
|
||||
buildRepresentation<P extends StructureRepresentationBuiltInProps>(builder: StateBuilder.Root, structure: StateObjectRef<PluginStateObject.Molecule.Structure> | undefined, props: P, options?: Partial<StructureRepresentationBuilder.AddRepresentationOptions>): StateObjectSelector<PluginStateObject.Molecule.Structure.Representation3D>
|
||||
buildRepresentation<P extends StructureRepresentationProps>(builder: StateBuilder.Root, structure: StateObjectRef<PluginStateObject.Molecule.Structure> | undefined, props: P, options?: Partial<StructureRepresentationBuilder.AddRepresentationOptions>): StateObjectSelector<PluginStateObject.Molecule.Structure.Representation3D>
|
||||
buildRepresentation(builder: StateBuilder.Root, structure: StateObjectRef<PluginStateObject.Molecule.Structure> | undefined, props: any, options?: Partial<StructureRepresentationBuilder.AddRepresentationOptions>) {
|
||||
if (!structure) return;
|
||||
const data = StateObjectRef.resolveAndCheck(this.dataState, structure)?.obj?.data;
|
||||
if (!data) return;
|
||||
|
||||
const params = createStructureRepresentationParams(this.plugin, data, props);
|
||||
return builder
|
||||
.to(structure)
|
||||
.apply(StructureRepresentation3D, params, { tags: StructureRepresentationBuilderTags.Representation })
|
||||
.selector;
|
||||
return options?.tag
|
||||
? builder.to(structure).applyOrUpdateTagged(options.tag, StructureRepresentation3D, params, { state: options?.initialState }).selector
|
||||
: builder.to(structure).apply(StructureRepresentation3D, params, { state: options?.initialState }).selector;
|
||||
}
|
||||
|
||||
constructor(public plugin: PluginContext) {
|
||||
objectForEach(PresetStructureReprentations, r => this.registerPreset(r));
|
||||
objectForEach(PresetStructureRepresentations, r => this.registerPreset(r));
|
||||
}
|
||||
}
|
||||
|
||||
export namespace StructureRepresentationBuilder {
|
||||
export interface AddRepresentationOptions {
|
||||
initialState?: Partial<StateTransform.State>,
|
||||
tag?: string
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,11 @@
|
||||
import { Camera } from '../mol-canvas3d/camera';
|
||||
import { OrderedMap } from 'immutable';
|
||||
import { UUID } from '../mol-util';
|
||||
import { PluginComponent } from './component';
|
||||
import { StatefulPluginComponent } from './component';
|
||||
|
||||
export { CameraSnapshotManager }
|
||||
|
||||
class CameraSnapshotManager extends PluginComponent<{ entries: OrderedMap<string, CameraSnapshotManager.Entry> }> {
|
||||
class CameraSnapshotManager extends StatefulPluginComponent<{ entries: OrderedMap<string, CameraSnapshotManager.Entry> }> {
|
||||
readonly events = {
|
||||
changed: this.ev()
|
||||
};
|
||||
|
||||
@@ -1,19 +1,36 @@
|
||||
/**
|
||||
* 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>
|
||||
*/
|
||||
|
||||
import { shallowMergeArray } from '../mol-util/object';
|
||||
import { RxEventHelper } from '../mol-util/rx-event-helper';
|
||||
import { Subscription, Observable } from 'rxjs';
|
||||
|
||||
export class PluginComponent<State> {
|
||||
export class PluginComponent {
|
||||
private _ev: RxEventHelper | undefined;
|
||||
private subs: Subscription[] | undefined = void 0;
|
||||
|
||||
protected subscribe<T>(obs: Observable<T>, action: (v: T) => void) {
|
||||
if (typeof this.subs === 'undefined') this.subs = [];
|
||||
this.subs.push(obs.subscribe(action));
|
||||
}
|
||||
|
||||
protected get ev() {
|
||||
return this._ev || (this._ev = RxEventHelper.create());
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this._ev) this._ev.dispose();
|
||||
if (this.subs) {
|
||||
for (const s of this.subs) s.unsubscribe();
|
||||
this.subs = void 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class StatefulPluginComponent<State> extends PluginComponent {
|
||||
private _state: State;
|
||||
|
||||
protected updateState(...states: Partial<State>[]): boolean {
|
||||
@@ -30,11 +47,8 @@ export class PluginComponent<State> {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this._ev) this._ev.dispose();
|
||||
}
|
||||
|
||||
constructor(initialState: State) {
|
||||
super();
|
||||
this._state = initialState;
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ export interface DataFormatProvider<P = any, R = any> {
|
||||
stringExtensions: string[]
|
||||
binaryExtensions: string[]
|
||||
isApplicable(info: FileInfo, data: string | Uint8Array): boolean
|
||||
parse(plugin: PluginContext, data: StateObjectRef<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, params: P | undefined): Promise<R>
|
||||
parse(plugin: PluginContext, data: StateObjectRef<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, params?: P): Promise<R>
|
||||
}
|
||||
|
||||
type cifVariants = 'dscif' | -1
|
||||
|
||||
@@ -11,7 +11,7 @@ import { guessCifVariant, DataFormatProvider, DataFormatRegistry } from './regis
|
||||
import { StateTransformer, StateObjectRef } from '../../mol-state';
|
||||
import { PluginStateObject } from '../objects';
|
||||
|
||||
export interface TrajectoryFormatProvider<P extends { trajectoryTags?: string | string[] } = { trajectoryTags?: string | string[] }, R extends { trajectory: StateObjectRef<PluginStateObject.Molecule.Trajectory> } = { trajectory: StateObjectRef<PluginStateObject.Molecule.Trajectory> }>
|
||||
export interface TrajectoryFormatProvider<P extends { trajectoryTags?: string | string[] } = { trajectoryTags?: string | string[] }, R extends { trajectory: StateObjectRef<PluginStateObject.Molecule.Trajectory> } = { trajectory: StateObjectRef<PluginStateObject.Molecule.Trajectory> }>
|
||||
extends DataFormatProvider<P, R> {
|
||||
}
|
||||
|
||||
@@ -32,10 +32,13 @@ export const MmcifProvider: TrajectoryFormatProvider = {
|
||||
},
|
||||
parse: async (plugin, data, params) => {
|
||||
const state = plugin.state.data;
|
||||
const trajectory = state.build().to(data)
|
||||
const cif = state.build().to(data)
|
||||
.apply(StateTransforms.Data.ParseCif, void 0, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.TrajectoryFromMmCif, void 0, { tags: params?.trajectoryTags })
|
||||
const trajectory = cif.apply(StateTransforms.Model.TrajectoryFromMmCif, void 0, { tags: params?.trajectoryTags })
|
||||
await plugin.updateDataState(trajectory, { revertOnError: true });
|
||||
if ((cif.selector.cell?.obj?.data.blocks.length || 0) > 1) {
|
||||
plugin.state.data.updateCellState(cif.ref, { isGhost: false });
|
||||
}
|
||||
return { trajectory: trajectory.selector };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +94,11 @@ export namespace RootStructureDefinition {
|
||||
|
||||
export type Params = PD.Values<ReturnType<typeof getParams>>['type']
|
||||
|
||||
export function canAutoUpdate(oldParams: Params, newParams: Params) {
|
||||
if (newParams.name === 'symmetry-assembly' || (newParams.name === 'symmetry' && oldParams.name === 'symmetry')) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
async function buildAssembly(plugin: PluginContext, ctx: RuntimeContext, model: Model, id?: string) {
|
||||
let asm: Assembly | undefined = void 0;
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ export function updateStructureComponent(a: Structure, b: SO.Molecule.Structure,
|
||||
return StateTransformer.UpdateResult.Recreate;
|
||||
}
|
||||
|
||||
if (a === cache.source) return StateTransformer.UpdateResult.Unchanged;
|
||||
if (a === cache.source) break;
|
||||
|
||||
const entry = (cache as { entry: StructureQueryHelper.CacheEntry }).entry;
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
*/
|
||||
|
||||
import { CustomProperty } from '../../mol-model-props/common/custom-property';
|
||||
import { AccessibleSurfaceAreaProvider, AccessibleSurfaceAreaSymbols } from '../../mol-model-props/computed/accessible-surface-area';
|
||||
import { ValidationReport, ValidationReportProvider } from '../../mol-model-props/rcsb/validation-report';
|
||||
import { QueryContext, Structure, StructureQuery, StructureSelection } from '../../mol-model/structure';
|
||||
import { BondType, NucleicBackboneAtoms, ProteinBackboneAtoms, SecondaryStructureType } from '../../mol-model/structure/model/types';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
@@ -382,57 +380,6 @@ const bonded = StructureSelectionQuery('Residues Bonded to Selection', MS.struct
|
||||
referencesCurrent: true
|
||||
})
|
||||
|
||||
const hasClash = StructureSelectionQuery('Residues with Clashes', MS.struct.modifier.union([
|
||||
MS.struct.modifier.wholeResidues([
|
||||
MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
|
||||
'atom-test': ValidationReport.symbols.hasClash.symbol(),
|
||||
})
|
||||
])
|
||||
])
|
||||
]), {
|
||||
description: 'Select residues with clashes in the wwPDB validation report.',
|
||||
category: StructureSelectionCategory.Residue,
|
||||
ensureCustomProperties: (ctx, structure) => {
|
||||
return ValidationReportProvider.attach(ctx, structure.models[0])
|
||||
}
|
||||
})
|
||||
|
||||
const isBuried = StructureSelectionQuery('Buried Protein Residues', MS.struct.modifier.union([
|
||||
MS.struct.modifier.wholeResidues([
|
||||
MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
|
||||
'residue-test': AccessibleSurfaceAreaSymbols.isBuried.symbol(),
|
||||
})
|
||||
])
|
||||
])
|
||||
]), {
|
||||
description: 'Select buried protein residues.',
|
||||
category: StructureSelectionCategory.Residue,
|
||||
ensureCustomProperties: (ctx, structure) => {
|
||||
return AccessibleSurfaceAreaProvider.attach(ctx, structure)
|
||||
}
|
||||
})
|
||||
|
||||
const isAccessible = StructureSelectionQuery('Accessible Protein Residues', MS.struct.modifier.union([
|
||||
MS.struct.modifier.wholeResidues([
|
||||
MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
|
||||
'residue-test': AccessibleSurfaceAreaSymbols.isAccessible.symbol(),
|
||||
})
|
||||
])
|
||||
])
|
||||
]), {
|
||||
description: 'Select accessible protein residues.',
|
||||
category: StructureSelectionCategory.Residue,
|
||||
ensureCustomProperties: (ctx, structure) => {
|
||||
return AccessibleSurfaceAreaProvider.attach(ctx, structure)
|
||||
}
|
||||
})
|
||||
|
||||
const StandardAminoAcids = [
|
||||
[['HIS'], 'HISTIDINE'],
|
||||
[['ARG'], 'ARGININE'],
|
||||
@@ -500,52 +447,41 @@ export const StructureSelectionQueries = {
|
||||
surroundings,
|
||||
complement,
|
||||
bonded,
|
||||
|
||||
hasClash,
|
||||
isBuried,
|
||||
isAccessible
|
||||
}
|
||||
|
||||
export const StructureSelectionQueryList = [
|
||||
...Object.values(StructureSelectionQueries),
|
||||
...StandardAminoAcids.map(v => ResidueQuery(v, StructureSelectionCategory.AminoAcid)),
|
||||
...StandardNucleicBases.map(v => ResidueQuery(v, StructureSelectionCategory.NucleicBase)),
|
||||
]
|
||||
|
||||
export const StructureSelectionQueryOptions: [StructureSelectionQuery, string, string][] = StructureSelectionQueryList.map(q => [q, q.label, q.category])
|
||||
|
||||
export function applyBuiltInSelection(to: StateBuilder.To<PluginStateObject.Molecule.Structure>, query: keyof typeof StructureSelectionQueries, customTag?: string) {
|
||||
return to.apply(StateTransforms.Model.StructureSelectionFromExpression,
|
||||
{ expression: StructureSelectionQueries[query].expression, label: StructureSelectionQueries[query].label },
|
||||
{ tags: customTag ? [query, customTag] : [query] });
|
||||
}
|
||||
|
||||
// namespace StructureSelectionQuery {
|
||||
// // export async function getStructure(plugin: PluginContext, runtime: RuntimeContext, selectionQuery: StructureSelectionQuery, structure: Structure) {
|
||||
// // const current = plugin.managers.structure.selection.getStructure(structure)
|
||||
// // const currentSelection = current ? StructureSelection.Singletons(structure, current) : StructureSelection.Empty(structure);
|
||||
export class StructureSelectionQueryRegistry {
|
||||
list: StructureSelectionQuery[] = []
|
||||
options: [StructureSelectionQuery, string, string][] = []
|
||||
version = 1
|
||||
|
||||
// // if (selectionQuery.ensureCustomProperties) {
|
||||
// // await selectionQuery.ensureCustomProperties({ fetch: plugin.fetch, runtime }, structure)
|
||||
// // }
|
||||
add(q: StructureSelectionQuery) {
|
||||
this.list.push(q)
|
||||
this.options.push([q, q.label, q.category])
|
||||
this.version += 1
|
||||
}
|
||||
|
||||
// // const result = selectionQuery.query(new QueryContext(structure, { currentSelection }))
|
||||
// // return StructureSelection.unionStructure(result)
|
||||
// // }
|
||||
remove(q: StructureSelectionQuery) {
|
||||
const idx = this.list.indexOf(q)
|
||||
if (idx !== -1) {
|
||||
this.list.splice(idx, 1)
|
||||
this.options.splice(idx, 1)
|
||||
this.version += 1
|
||||
}
|
||||
}
|
||||
|
||||
// // export async function getSelection(plugin: PluginContext, runtime: RuntimeContext, selectionQuery: StructureSelectionQuery, structure: Structure) {
|
||||
// // const current = plugin.managers.structure.selection.getStructure(structure)
|
||||
// // const currentSelection = current ? StructureSelection.Singletons(structure, current) : StructureSelection.Empty(structure);
|
||||
|
||||
// // if (selectionQuery.ensureCustomProperties) {
|
||||
// // await selectionQuery.ensureCustomProperties({ fetch: plugin.fetch, runtime }, structure)
|
||||
// // }
|
||||
|
||||
// // return selectionQuery.query(new QueryContext(structure, { currentSelection }));
|
||||
// // }
|
||||
|
||||
// // export async function getBundle(plugin: PluginContext, runtime: RuntimeContext, selectionQuery: StructureSelectionQuery, structure: Structure) {
|
||||
// // const loci = await getLoci(plugin, runtime, selectionQuery, structure);
|
||||
// // return StructureElement.Bundle.fromLoci(loci);
|
||||
// // }
|
||||
// }
|
||||
constructor() {
|
||||
// add built-in
|
||||
this.list.push(
|
||||
...Object.values(StructureSelectionQueries),
|
||||
...StandardAminoAcids.map(v => ResidueQuery(v, StructureSelectionCategory.AminoAcid)),
|
||||
...StandardNucleicBases.map(v => ResidueQuery(v, StructureSelectionCategory.NucleicBase))
|
||||
)
|
||||
this.options.push(...this.list.map(q => [q, q.label, q.category] as [StructureSelectionQuery, string, string]))
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ 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';
|
||||
import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
|
||||
|
||||
// TODO: make this customizable somewhere?
|
||||
const DefaultCameraFocusOptions = {
|
||||
@@ -21,16 +22,64 @@ const DefaultCameraFocusOptions = {
|
||||
export type CameraFocusOptions = typeof DefaultCameraFocusOptions
|
||||
|
||||
export class CameraManager {
|
||||
focusLoci(loci: Loci, options?: Partial<CameraFocusOptions>) {
|
||||
private boundaryHelper = new BoundaryHelper('98');
|
||||
|
||||
focusLoci(loci: 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 = Loci.getBoundingSphere(loci);
|
||||
if (sphere) {
|
||||
const radius = Math.max(sphere.radius + extraRadius, minRadius);
|
||||
this.plugin.canvas3d?.camera.focus(sphere.center, radius, durationMs);
|
||||
let sphere: Sphere3D | undefined;
|
||||
|
||||
if (Array.isArray(loci) && loci.length > 1) {
|
||||
const spheres = [];
|
||||
for (const l of loci) {
|
||||
const s = Loci.getBoundingSphere(l);
|
||||
if (s) spheres.push(s);
|
||||
}
|
||||
|
||||
if (spheres.length === 0) return;
|
||||
|
||||
this.boundaryHelper.reset();
|
||||
for (const s of spheres) {
|
||||
this.boundaryHelper.includeSphere(s);
|
||||
}
|
||||
this.boundaryHelper.finishedIncludeStep();
|
||||
for (const s of spheres) {
|
||||
this.boundaryHelper.radiusSphere(s);
|
||||
}
|
||||
sphere = this.boundaryHelper.getSphere();
|
||||
} else if (Array.isArray(loci)) {
|
||||
if (loci.length === 0) return;
|
||||
sphere = Loci.getBoundingSphere(loci[0]);
|
||||
} else {
|
||||
sphere = Loci.getBoundingSphere(loci);
|
||||
}
|
||||
|
||||
if (sphere) {
|
||||
this.focusSphere(sphere, options);
|
||||
}
|
||||
}
|
||||
|
||||
focusSpheres<T>(xs: ReadonlyArray<T>, sphere: (t: T) => Sphere3D | undefined, options?: Partial<CameraFocusOptions>) {
|
||||
const spheres = [];
|
||||
|
||||
for (const x of xs) {
|
||||
const s = sphere(x);
|
||||
if (s) spheres.push(s);
|
||||
}
|
||||
|
||||
if (spheres.length === 0) return;
|
||||
if (spheres.length === 1) return this.focusSphere(spheres[0], options);
|
||||
|
||||
this.boundaryHelper.reset();
|
||||
for (const s of spheres) {
|
||||
this.boundaryHelper.includeSphere(s);
|
||||
}
|
||||
this.boundaryHelper.finishedIncludeStep();
|
||||
for (const s of spheres) {
|
||||
this.boundaryHelper.radiusSphere(s);
|
||||
}
|
||||
this.focusSphere(this.boundaryHelper.getSphere(), options);
|
||||
}
|
||||
|
||||
focusSphere(sphere: Sphere3D, options?: Partial<CameraFocusOptions> & { principalAxes?: PrincipalAxes }) {
|
||||
@@ -39,15 +88,20 @@ export class CameraManager {
|
||||
|
||||
if (options?.principalAxes) {
|
||||
const { origin, dirA, dirC } = options?.principalAxes.boxAxes;
|
||||
this.plugin.canvas3d?.camera.focus(origin, radius, durationMs, dirA, dirC);
|
||||
const snapshot = this.plugin.canvas3d?.camera.getFocus(origin, radius, dirA, dirC);
|
||||
this.plugin.canvas3d?.requestCameraReset({ durationMs, snapshot })
|
||||
// this.plugin.canvas3d?.camera.focus(origin, radius, durationMs, dirA, dirC);
|
||||
} else {
|
||||
this.plugin.canvas3d?.camera.focus(sphere.center, radius, durationMs);
|
||||
const snapshot = this.plugin.canvas3d?.camera.getFocus(sphere.center, radius);
|
||||
this.plugin.canvas3d?.requestCameraReset({ durationMs, snapshot })
|
||||
|
||||
// this.plugin.canvas3d?.camera.focus(sphere.center, radius, durationMs);
|
||||
}
|
||||
}
|
||||
|
||||
setSnapshot(snapshot: Partial<Camera.Snapshot>, durationMs?: number) {
|
||||
// TODO: setState and requestCameraReset are very similar now: unify them?
|
||||
this.plugin.canvas3d?.camera.setState(snapshot, durationMs);
|
||||
this.plugin.canvas3d?.requestCameraReset({ snapshot, durationMs });
|
||||
}
|
||||
|
||||
reset(snapshot?: Partial<Camera.Snapshot>, durationMs?: number) {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ButtonsType, ModifiersKeys } from '../../mol-util/input/input-observer'
|
||||
import { MarkerAction } from '../../mol-util/marker-action';
|
||||
import { shallowEqual } from '../../mol-util/object';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { PluginComponent } from '../component';
|
||||
import { StatefulPluginComponent } from '../component';
|
||||
import { StructureSelectionManager } from './structure/selection';
|
||||
|
||||
export { InteractivityManager };
|
||||
@@ -31,7 +31,7 @@ const DefaultInteractivityFocusOptions = {
|
||||
|
||||
export type InteractivityFocusLociOptions = typeof DefaultInteractivityFocusOptions
|
||||
|
||||
class InteractivityManager extends PluginComponent<InteractivityManagerState> {
|
||||
class InteractivityManager extends StatefulPluginComponent<InteractivityManagerState> {
|
||||
readonly lociSelects: InteractivityManager.LociSelectManager;
|
||||
readonly lociHighlights: InteractivityManager.LociHighlightManager;
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ export class LociLabelManager {
|
||||
|
||||
private isDirty = false
|
||||
private entries: LociLabelEntry[] = []
|
||||
private entriesCounts = new Map<LociLabelEntry, number>()
|
||||
|
||||
private showLabels() {
|
||||
this.ctx.behaviors.labels.highlight.next({ entries: this.getEntries() })
|
||||
@@ -51,14 +52,22 @@ export class LociLabelManager {
|
||||
|
||||
private getEntries() {
|
||||
if (this.isDirty) {
|
||||
this.entriesCounts.clear()
|
||||
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)
|
||||
if (entry) {
|
||||
const count = this.entriesCounts.get(entry) || 0
|
||||
this.entriesCounts.set(entry, count + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.entries.length = 0
|
||||
this.entriesCounts.forEach((count, entry) => {
|
||||
this.entries.push(count === 1 ? entry : `${entry} (\u00D7 ${count})`)
|
||||
})
|
||||
this.isDirty = false
|
||||
}
|
||||
return this.entries
|
||||
|
||||
@@ -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 David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -7,24 +7,25 @@
|
||||
import { VisualQualityOptions } from '../../../mol-geo/geometry/base';
|
||||
import { InteractionsProvider } from '../../../mol-model-props/computed/interactions';
|
||||
import { Structure, StructureElement } from '../../../mol-model/structure';
|
||||
import { structureAreIntersecting, structureSubtract, structureUnion, structureIntersect } from '../../../mol-model/structure/query/utils/structure-set';
|
||||
import { structureAreIntersecting, structureIntersect, structureSubtract, structureUnion } from '../../../mol-model/structure/query/utils/structure-set';
|
||||
import { setSubtreeVisibility } from '../../../mol-plugin/behavior/static/state';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { StateBuilder, StateTransformer } from '../../../mol-state';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { UUID } from '../../../mol-util';
|
||||
import { ColorNames } from '../../../mol-util/color/names';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { StructureRepresentationPresetProvider } from '../../builder/structure/representation-preset';
|
||||
import { PluginComponent } from '../../component';
|
||||
import { StructureComponentParams } from '../../helpers/structure-component';
|
||||
import { clearStructureOverpaint, setStructureOverpaint } from '../../helpers/structure-overpaint';
|
||||
import { StructureSelectionQueries, StructureSelectionQuery, StructureSelectionQueryOptions } from '../../helpers/structure-selection-query';
|
||||
import { StructureRepresentation3D } from '../../transforms/representation';
|
||||
import { HierarchyRef, StructureComponentRef, StructureRef, StructureRepresentationRef } from './hierarchy-state';
|
||||
import { createStructureColorThemeParams, createStructureSizeThemeParams } from '../../helpers/structure-representation-params';
|
||||
import { ColorTheme } from '../../../mol-theme/color';
|
||||
import { SizeTheme } from '../../../mol-theme/size';
|
||||
import { UUID } from '../../../mol-util';
|
||||
import { ColorNames } from '../../../mol-util/color/names';
|
||||
import { objectForEach } from '../../../mol-util/object';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { StructureRepresentationPresetProvider } from '../../builder/structure/representation-preset';
|
||||
import { StatefulPluginComponent } from '../../component';
|
||||
import { StructureComponentParams } from '../../helpers/structure-component';
|
||||
import { clearStructureOverpaint, setStructureOverpaint } from '../../helpers/structure-overpaint';
|
||||
import { createStructureColorThemeParams, createStructureSizeThemeParams } from '../../helpers/structure-representation-params';
|
||||
import { StructureSelectionQueries, StructureSelectionQuery } from '../../helpers/structure-selection-query';
|
||||
import { StructureRepresentation3D } from '../../transforms/representation';
|
||||
import { HierarchyRef, StructureComponentRef, StructureRef, StructureRepresentationRef } from './hierarchy-state';
|
||||
|
||||
export { StructureComponentManager };
|
||||
|
||||
@@ -32,13 +33,13 @@ interface StructureComponentManagerState {
|
||||
options: StructureComponentManager.Options
|
||||
}
|
||||
|
||||
class StructureComponentManager extends PluginComponent<StructureComponentManagerState> {
|
||||
class StructureComponentManager extends StatefulPluginComponent<StructureComponentManagerState> {
|
||||
readonly events = {
|
||||
optionsUpdated: this.ev<undefined>()
|
||||
}
|
||||
|
||||
get currentStructures() {
|
||||
return this.plugin.managers.structure.hierarchy.state.selection.structures;
|
||||
return this.plugin.managers.structure.hierarchy.selection.structures;
|
||||
}
|
||||
|
||||
get pivotStructure(): StructureRef | undefined {
|
||||
@@ -56,8 +57,6 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
|
||||
for (const c of s.components) {
|
||||
this.updateReprParams(update, c);
|
||||
}
|
||||
if (s.currentFocus?.focus) this.updateReprParams(update, s.currentFocus.focus);
|
||||
if (s.currentFocus?.surroundings) this.updateReprParams(update, s.currentFocus.surroundings);
|
||||
}
|
||||
|
||||
return this.plugin.dataTransaction(async () => {
|
||||
@@ -105,15 +104,58 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
|
||||
}
|
||||
}
|
||||
|
||||
applyPreset<P = any, S = {}>(structures: ReadonlyArray<StructureRef>, provider: StructureRepresentationPresetProvider<P, S>, params?: P): Promise<any> {
|
||||
applyPreset<P extends StructureRepresentationPresetProvider>(structures: ReadonlyArray<StructureRef>, provider: P, params?: StructureRepresentationPresetProvider.Params<P>): Promise<any> {
|
||||
return this.plugin.dataTransaction(async () => {
|
||||
await this.clearComponents(structures);
|
||||
for (const s of structures) {
|
||||
await this.plugin.builders.structure.representation.applyPreset(s.childRoot, provider, params);
|
||||
const preset = await this.plugin.builders.structure.representation.applyPreset(s.cell, provider, params);
|
||||
await this.syncPreset(s, preset);
|
||||
}
|
||||
}, { canUndo: 'Preset' });
|
||||
}
|
||||
|
||||
private async syncPreset(root: StructureRef, preset?: StructureRepresentationPresetProvider.Result) {
|
||||
if (!preset || !preset.components) return this.clearComponents([root]);
|
||||
|
||||
const keptRefs = new Set<string>();
|
||||
objectForEach(preset.components, c => {
|
||||
if (c) keptRefs.add(c.ref);
|
||||
});
|
||||
|
||||
if (preset.representations) {
|
||||
objectForEach(preset.representations, r => {
|
||||
if (r) keptRefs.add(r.ref);
|
||||
});
|
||||
}
|
||||
|
||||
if (keptRefs.size === 0) return this.clearComponents([root]);
|
||||
|
||||
let changed = false;
|
||||
const update = this.dataState.build();
|
||||
|
||||
const sync = (r: HierarchyRef) => {
|
||||
if (!keptRefs.has(r.cell.transform.ref)) {
|
||||
changed = true;
|
||||
update.delete(r.cell);
|
||||
}
|
||||
};
|
||||
|
||||
for (const c of root.components) {
|
||||
sync(c);
|
||||
for (const r of c.representations) sync(r);
|
||||
if (c.genericRepresentations) {
|
||||
for (const r of c.genericRepresentations) sync(r);
|
||||
}
|
||||
}
|
||||
|
||||
if (root.genericRepresentations) {
|
||||
for (const r of root.genericRepresentations) {
|
||||
sync(r);
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) return this.plugin.updateDataState(update);
|
||||
}
|
||||
|
||||
clear(structures: ReadonlyArray<StructureRef>) {
|
||||
return this.clearComponents(structures);
|
||||
}
|
||||
@@ -273,7 +315,7 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
|
||||
|
||||
const componentKey = UUID.create22();
|
||||
for (const s of xs) {
|
||||
const component = await this.plugin.builders.structure.tryCreateComponentFromSelection(s.childRoot, params.selection, componentKey, {
|
||||
const component = await this.plugin.builders.structure.tryCreateComponentFromSelection(s.cell, params.selection, componentKey, {
|
||||
label: params.label || (params.selection === StructureSelectionQueries.current ? 'Custom Selection' : ''),
|
||||
});
|
||||
if (params.representation === 'none' || !component) continue;
|
||||
@@ -336,10 +378,6 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
|
||||
for (const c of s.components) {
|
||||
deletes.delete(c.cell.transform.ref);
|
||||
}
|
||||
if (s.currentFocus) {
|
||||
if (s.currentFocus.focus) deletes.delete(s.currentFocus.focus.cell.transform.ref);
|
||||
if (s.currentFocus.surroundings) deletes.delete(s.currentFocus.surroundings.cell.transform.ref);
|
||||
}
|
||||
}
|
||||
return this.plugin.updateDataState(deletes, { canUndo: 'Clear Selections' });
|
||||
}
|
||||
@@ -357,11 +395,10 @@ namespace StructureComponentManager {
|
||||
}
|
||||
export type Options = PD.Values<typeof OptionsParams>
|
||||
|
||||
const SelectionParam = PD.Select(StructureSelectionQueryOptions[1][0], StructureSelectionQueryOptions)
|
||||
|
||||
export function getAddParams(plugin: PluginContext) {
|
||||
const { options } = plugin.query.structure.registry
|
||||
return {
|
||||
selection: SelectionParam,
|
||||
selection: PD.Select(options[1][0], options),
|
||||
representation: getRepresentationTypesSelect(plugin, plugin.managers.structure.component.pivotStructure, [['none', '< Create Later >']]),
|
||||
label: PD.Text('')
|
||||
};
|
||||
|
||||
124
src/mol-plugin-state/manager/structure/focus.ts
Normal file
124
src/mol-plugin-state/manager/structure/focus.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Copyright (c) 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 { StatefulPluginComponent } from '../../component';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { arrayRemoveAtInPlace } from '../../../mol-util/array';
|
||||
import { StructureElement } from '../../../mol-model/structure';
|
||||
import { Loci } from '../../../mol-model/loci';
|
||||
import { lociLabel } from '../../../mol-theme/label';
|
||||
import { PluginStateObject } from '../../objects';
|
||||
|
||||
export type FocusEntry = {
|
||||
label: string
|
||||
loci: StructureElement.Loci
|
||||
category?: string
|
||||
}
|
||||
|
||||
interface StructureFocusManagerState {
|
||||
current?: FocusEntry
|
||||
history: FocusEntry[]
|
||||
}
|
||||
|
||||
const HISTORY_CAPACITY = 4;
|
||||
|
||||
export class StructureFocusManager extends StatefulPluginComponent<StructureFocusManagerState> {
|
||||
|
||||
readonly events = {
|
||||
historyUpdated: this.ev<undefined>()
|
||||
}
|
||||
|
||||
readonly behaviors = {
|
||||
current: this.ev.behavior<FocusEntry | undefined>(void 0)
|
||||
};
|
||||
|
||||
get current() { return this.state.current; }
|
||||
get history() { return this.state.history; }
|
||||
|
||||
/** Adds to history without `.category` */
|
||||
private tryAddHistory(entry: FocusEntry) {
|
||||
const { label, loci } = entry
|
||||
if (StructureElement.Loci.isEmpty(loci)) return;
|
||||
|
||||
let idx = 0, existingEntry: FocusEntry | undefined = void 0;
|
||||
for (const e of this.state.history) {
|
||||
if (StructureElement.Loci.areEqual(e.loci, loci)) {
|
||||
existingEntry = e;
|
||||
break;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (existingEntry) {
|
||||
// move to top, use new
|
||||
arrayRemoveAtInPlace(this.state.history, idx);
|
||||
this.state.history.unshift({ label, loci });
|
||||
this.events.historyUpdated.next();
|
||||
return;
|
||||
}
|
||||
|
||||
this.state.history.unshift({ label, loci });
|
||||
if (this.state.history.length > HISTORY_CAPACITY) this.state.history.pop();
|
||||
|
||||
this.events.historyUpdated.next();
|
||||
}
|
||||
|
||||
set(entry: FocusEntry) {
|
||||
this.tryAddHistory(entry)
|
||||
if (!this.state.current || !StructureElement.Loci.areEqual(this.state.current.loci, entry.loci)) {
|
||||
this.state.current = entry
|
||||
this.behaviors.current.next(entry)
|
||||
}
|
||||
}
|
||||
|
||||
setFromLoci(anyLoci: Loci) {
|
||||
const loci = Loci.normalize(anyLoci)
|
||||
if (!StructureElement.Loci.is(loci) || StructureElement.Loci.isEmpty(loci)) {
|
||||
this.clear()
|
||||
return
|
||||
}
|
||||
|
||||
this.set({ loci, label: lociLabel(loci, { reverse: true, hidePrefix: true, htmlStyling: false }) })
|
||||
}
|
||||
|
||||
clear() {
|
||||
if (this.state.current) {
|
||||
this.state.current = undefined
|
||||
this.behaviors.current.next(void 0)
|
||||
}
|
||||
}
|
||||
|
||||
// this.subscribeObservable(this.plugin.events.state.object.updated, o => {
|
||||
// if (!PluginStateObject.Molecule.Structure.is(o.oldObj) || !StructureElement.Loci.is(lastLoci)) return;
|
||||
// if (lastLoci.structure === o.oldObj.data) {
|
||||
// lastLoci = EmptyLoci;
|
||||
// }
|
||||
// });
|
||||
|
||||
constructor(plugin: PluginContext) {
|
||||
super({ history: [] });
|
||||
|
||||
plugin.state.data.events.object.removed.subscribe(o => {
|
||||
if (!PluginStateObject.Molecule.Structure.is(o.obj)) return
|
||||
|
||||
if (this.current?.loci.structure === o.obj.data) {
|
||||
this.clear()
|
||||
}
|
||||
|
||||
const keep: FocusEntry[] = []
|
||||
for (const e of this.history) {
|
||||
if (e.loci.structure === o.obj.data) keep.push(e)
|
||||
}
|
||||
if (keep.length !== this.history.length) {
|
||||
this.history.length = 0
|
||||
this.history.push(...keep)
|
||||
this.events.historyUpdated.next()
|
||||
}
|
||||
});
|
||||
// plugin.state.data.events.object.updated.subscribe(e => this.onUpdate(e.ref, e.oldObj, e.obj));
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,6 @@
|
||||
|
||||
import { PluginStateObject as SO } from '../../objects';
|
||||
import { StateObject, StateTransform, State, StateObjectCell, StateTree, StateTransformer } from '../../../mol-state';
|
||||
import { StructureBuilderTags } from '../../builder/structure';
|
||||
import { StructureRepresentationBuilderTags } from '../../builder/structure/representation';
|
||||
import { StructureRepresentationInteractionTags } from '../../../mol-plugin/behavior/dynamic/selection/structure-representation-interaction';
|
||||
import { StateTransforms } from '../../transforms';
|
||||
import { VolumeStreaming } from '../../../mol-plugin/behavior/dynamic/volume-streaming/behavior';
|
||||
import { CreateVolumeStreamingBehavior } from '../../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
|
||||
@@ -56,13 +53,11 @@ 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>
|
||||
unitcell?: ModelUnitcellRef
|
||||
}
|
||||
|
||||
function ModelRef(cell: StateObjectCell<SO.Molecule.Model>, trajectory?: TrajectoryRef): ModelRef {
|
||||
return { kind: 'model', cell, version: cell.transform.version, trajectory, structures: [], childRoot: cell };
|
||||
return { kind: 'model', cell, version: cell.transform.version, trajectory, structures: [] };
|
||||
}
|
||||
|
||||
export interface ModelPropertiesRef extends RefBase<'model-properties', SO.Molecule.Model, StateTransforms['Model']['CustomModelProperties']> {
|
||||
@@ -85,18 +80,12 @@ export interface StructureRef extends RefBase<'structure', SO.Molecule.Structure
|
||||
model?: ModelRef,
|
||||
properties?: StructurePropertiesRef,
|
||||
components: StructureComponentRef[],
|
||||
currentFocus?: {
|
||||
focus?: StructureComponentRef,
|
||||
surroundings?: StructureComponentRef,
|
||||
},
|
||||
genericRepresentations?: GenericRepresentationRef[],
|
||||
volumeStreaming?: StructureVolumeStreamingRef,
|
||||
/** to support decorators */
|
||||
childRoot: StateObjectCell<SO.Molecule.Structure>
|
||||
volumeStreaming?: StructureVolumeStreamingRef
|
||||
}
|
||||
|
||||
function StructureRef(cell: StateObjectCell<SO.Molecule.Structure>, model?: ModelRef): StructureRef {
|
||||
return { kind: 'structure', cell, version: cell.transform.version, model, components: [], childRoot: cell };
|
||||
return { kind: 'structure', cell, version: cell.transform.version, model, components: [] };
|
||||
}
|
||||
|
||||
export interface StructurePropertiesRef extends RefBase<'structure-properties', SO.Molecule.Structure, StateTransforms['Model']['CustomStructureProperties']> {
|
||||
@@ -123,7 +112,7 @@ export interface StructureComponentRef extends RefBase<'structure-component', SO
|
||||
}
|
||||
|
||||
function componentKey(cell: StateObjectCell<SO.Molecule.Structure>) {
|
||||
if (!cell.transform.tags) return;
|
||||
if (!cell.transform.tags) return cell.transform.ref;
|
||||
return [...cell.transform.tags].sort().join();
|
||||
}
|
||||
|
||||
@@ -193,11 +182,32 @@ function createOrUpdateRef<R extends HierarchyRef, C extends any[]>(state: Build
|
||||
return ref;
|
||||
}
|
||||
|
||||
const tagMap: [string, (state: BuildState, cell: StateObjectCell) => boolean | void, (state: BuildState) => any][] = [
|
||||
[StructureBuilderTags.Trajectory, (state, cell) => {
|
||||
type TestCell = (cell: StateObjectCell, state: BuildState) => boolean
|
||||
type ApplyRef = (state: BuildState, cell: StateObjectCell) => boolean | void
|
||||
type LeaveRef = (state: BuildState) => any
|
||||
|
||||
function isType(t: StateObject.Ctor): TestCell {
|
||||
return (cell) => t.is(cell.obj);
|
||||
}
|
||||
|
||||
function isTypeRoot(t: StateObject.Ctor, target: (state: BuildState) => any): TestCell {
|
||||
return (cell, state) => !target(state) && t.is(cell.obj);
|
||||
}
|
||||
|
||||
function isTransformer(t: StateTransformer): TestCell {
|
||||
return cell => cell.transform.transformer === t;
|
||||
}
|
||||
|
||||
function noop() { }
|
||||
|
||||
const Mapping: [TestCell, ApplyRef, LeaveRef][] = [
|
||||
// Trajectory
|
||||
[isType(SO.Molecule.Trajectory), (state, cell) => {
|
||||
state.currentTrajectory = createOrUpdateRefList(state, cell, state.hierarchy.trajectories, TrajectoryRef, cell);
|
||||
}, state => state.currentTrajectory = void 0],
|
||||
[StructureBuilderTags.Model, (state, cell) => {
|
||||
|
||||
// Model
|
||||
[isTypeRoot(SO.Molecule.Model, s => s.currentModel), (state, cell) => {
|
||||
if (state.currentTrajectory) {
|
||||
state.currentModel = createOrUpdateRefList(state, cell, state.currentTrajectory.models, ModelRef, cell, state.currentTrajectory);
|
||||
} else {
|
||||
@@ -205,20 +215,17 @@ const tagMap: [string, (state: BuildState, cell: StateObjectCell) => boolean | v
|
||||
}
|
||||
state.hierarchy.models.push(state.currentModel);
|
||||
}, state => state.currentModel = void 0],
|
||||
[StructureBuilderTags.ModelProperties, (state, cell) => {
|
||||
[isTransformer(StateTransforms.Model.CustomModelProperties), (state, cell) => {
|
||||
if (!state.currentModel) return false;
|
||||
state.currentModel.properties = createOrUpdateRef(state, cell, ModelPropertiesRef, cell, state.currentModel);
|
||||
}, state => { }],
|
||||
[StructureBuilderTags.ModelUnitcell, (state, cell) => {
|
||||
}, noop],
|
||||
[isTransformer(StateTransforms.Representation.ModelUnitcell3D), (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) => {
|
||||
}, noop],
|
||||
|
||||
// Structure
|
||||
[isTypeRoot(SO.Molecule.Structure, s => s.currentStructure), (state, cell) => {
|
||||
if (state.currentModel) {
|
||||
state.currentStructure = createOrUpdateRefList(state, cell, state.currentModel.structures, StructureRef, cell, state.currentModel);
|
||||
} else {
|
||||
@@ -226,30 +233,49 @@ const tagMap: [string, (state: BuildState, cell: StateObjectCell) => boolean | v
|
||||
}
|
||||
state.hierarchy.structures.push(state.currentStructure);
|
||||
}, state => state.currentStructure = void 0],
|
||||
[StructureBuilderTags.StructureProperties, (state, cell) => {
|
||||
[isTransformer(StateTransforms.Model.CustomStructureProperties), (state, cell) => {
|
||||
if (!state.currentStructure) return false;
|
||||
state.currentStructure.properties = createOrUpdateRef(state, cell, StructurePropertiesRef, cell, state.currentStructure);
|
||||
}, state => { }],
|
||||
[StructureBuilderTags.Component, (state, cell) => {
|
||||
}, noop],
|
||||
|
||||
// Volume Streaming
|
||||
[isType(VolumeStreaming), (state, cell) => {
|
||||
if (!state.currentStructure) return false;
|
||||
state.currentComponent = createOrUpdateRefList(state, cell, state.currentStructure.components, StructureComponentRef, cell, state.currentStructure);
|
||||
state.currentStructure.volumeStreaming = createOrUpdateRef(state, cell, StructureVolumeStreamingRef, cell, state.currentStructure);
|
||||
// Do not continue into VolumeStreaming subtree.
|
||||
return false;
|
||||
}, noop],
|
||||
|
||||
// Component
|
||||
[(cell, state) => {
|
||||
if (state.currentComponent || !state.currentStructure || cell.transform.transformer.definition.isDecorator) return false;
|
||||
return SO.Molecule.Structure.is(cell.obj);
|
||||
}, (state, cell) => {
|
||||
if (state.currentStructure) {
|
||||
state.currentComponent = createOrUpdateRefList(state, cell, state.currentStructure.components, StructureComponentRef, cell, state.currentStructure);
|
||||
}
|
||||
}, state => state.currentComponent = void 0],
|
||||
[StructureRepresentationBuilderTags.Representation, (state, cell) => {
|
||||
if (!state.currentComponent) return false;
|
||||
createOrUpdateRefList(state, cell, state.currentComponent.representations, StructureRepresentationRef, cell, state.currentComponent);
|
||||
}, state => { }],
|
||||
[StructureRepresentationInteractionTags.ResidueSel, (state, cell) => {
|
||||
if (!state.currentStructure) return false;
|
||||
if (!state.currentStructure.currentFocus) state.currentStructure.currentFocus = { };
|
||||
state.currentStructure.currentFocus.focus = createOrUpdateRef(state, cell, StructureComponentRef, cell, state.currentStructure);
|
||||
state.currentComponent = state.currentStructure.currentFocus.focus;
|
||||
}, state => state.currentComponent = void 0],
|
||||
[StructureRepresentationInteractionTags.SurrSel, (state, cell) => {
|
||||
if (!state.currentStructure) return false;
|
||||
if (!state.currentStructure.currentFocus) state.currentStructure.currentFocus = { };
|
||||
state.currentStructure.currentFocus.surroundings = createOrUpdateRef(state, cell, StructureComponentRef, cell, state.currentStructure);
|
||||
state.currentComponent = state.currentStructure.currentFocus.surroundings;
|
||||
}, state => state.currentComponent = void 0]
|
||||
|
||||
// Component Representation
|
||||
[(cell, state) => {
|
||||
return !cell.state.isGhost && !!state.currentComponent && SO.Molecule.Structure.Representation3D.is(cell.obj)
|
||||
}, (state, cell) => {
|
||||
if (state.currentComponent) {
|
||||
createOrUpdateRefList(state, cell, state.currentComponent.representations, StructureRepresentationRef, cell, state.currentComponent);
|
||||
}
|
||||
|
||||
// Nothing useful down the line
|
||||
return false;
|
||||
}, noop],
|
||||
|
||||
// Generic Representation
|
||||
[cell => !cell.state.isGhost && SO.isRepresentation3D(cell.obj), (state, cell) => {
|
||||
const genericTarget = state.currentComponent || state.currentStructure || state.currentModel;
|
||||
if (genericTarget) {
|
||||
if (!genericTarget.genericRepresentations) genericTarget.genericRepresentations = [];
|
||||
createOrUpdateRefList(state, cell, genericTarget.genericRepresentations, GenericRepresentationRef, cell, genericTarget);
|
||||
}
|
||||
}, noop],
|
||||
]
|
||||
|
||||
function isValidCell(cell?: StateObjectCell): cell is StateObjectCell {
|
||||
@@ -274,10 +300,10 @@ function _doPreOrder(ctx: VisitorCtx, root: StateTransform) {
|
||||
if (!isValidCell(cell)) return;
|
||||
|
||||
let onLeave: undefined | ((state: BuildState) => any) = void 0;
|
||||
for (const [t, f, l] of tagMap) {
|
||||
if (StateObject.hasTag(cell.obj!, t)) {
|
||||
const stop = f(state, cell);
|
||||
if (stop === false) {
|
||||
for (const [test, f, l] of Mapping) {
|
||||
if (test(cell, state)) {
|
||||
const cont = f(state, cell);
|
||||
if (cont === false) {
|
||||
return;
|
||||
}
|
||||
onLeave = l;
|
||||
@@ -285,27 +311,6 @@ function _doPreOrder(ctx: VisitorCtx, root: StateTransform) {
|
||||
}
|
||||
}
|
||||
|
||||
if (cell.transform.isDecorator) {
|
||||
if (state.currentModel && SO.Molecule.Model.is(cell.obj)) {
|
||||
state.currentModel.childRoot = cell;
|
||||
} else if (state.currentStructure && !state.currentComponent && SO.Molecule.Structure.is(cell.obj)) {
|
||||
state.currentStructure.childRoot = cell;
|
||||
}
|
||||
}
|
||||
|
||||
if (!onLeave && !cell.state.isGhost && state.currentComponent && SO.Molecule.Structure.Representation3D.is(cell.obj)) {
|
||||
createOrUpdateRefList(state, cell, state.currentComponent.representations, StructureRepresentationRef, cell, state.currentComponent);
|
||||
} else if (!cell.state.isGhost && SO.isRepresentation3D(cell.obj)) {
|
||||
const genericTarget = state.currentComponent || state.currentModel || state.currentStructure;
|
||||
if (genericTarget) {
|
||||
if (!genericTarget.genericRepresentations) genericTarget.genericRepresentations = [];
|
||||
createOrUpdateRefList(state, cell, genericTarget.genericRepresentations, GenericRepresentationRef, cell, genericTarget);
|
||||
}
|
||||
} else if (state.currentStructure && VolumeStreaming.is(cell.obj)) {
|
||||
state.currentStructure.volumeStreaming = createOrUpdateRef(state, cell, StructureVolumeStreamingRef, cell, state.currentStructure);
|
||||
return;
|
||||
}
|
||||
|
||||
const children = ctx.tree.children.get(root.ref);
|
||||
if (children && children.size) {
|
||||
children.forEach(_preOrderFunc, ctx);
|
||||
|
||||
@@ -2,31 +2,36 @@
|
||||
* 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 { setSubtreeVisibility } from '../../../mol-plugin/behavior/static/state';
|
||||
import { PluginCommands } from '../../../mol-plugin/commands';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { StructureHierarchy, buildStructureHierarchy, ModelRef, StructureComponentRef, StructureRef, HierarchyRef, TrajectoryRef } from './hierarchy-state';
|
||||
import { PluginComponent } from '../../component';
|
||||
import { StateTransform, StateTree } from '../../../mol-state';
|
||||
import { SetUtils } from '../../../mol-util/set';
|
||||
import { StateTransform } from '../../../mol-state';
|
||||
import { applyTrajectoryHierarchyPreset } from '../../builder/structure/hierarchy-preset';
|
||||
import { TrajectoryHierarchyPresetProvider } from '../../builder/structure/hierarchy-preset';
|
||||
import { PluginComponent } from '../../component';
|
||||
import { buildStructureHierarchy, HierarchyRef, ModelRef, StructureComponentRef, StructureHierarchy, StructureRef, TrajectoryRef } from './hierarchy-state';
|
||||
|
||||
interface StructureHierarchyManagerState {
|
||||
hierarchy: StructureHierarchy,
|
||||
selection: {
|
||||
trajectories: ReadonlyArray<TrajectoryRef>,
|
||||
models: ReadonlyArray<ModelRef>,
|
||||
structures: ReadonlyArray<StructureRef>
|
||||
export class StructureHierarchyManager extends PluginComponent {
|
||||
private state = {
|
||||
syncedTree: this.dataState.tree,
|
||||
|
||||
hierarchy: StructureHierarchy(),
|
||||
selection: {
|
||||
trajectories: [] as ReadonlyArray<TrajectoryRef>,
|
||||
models: [] as ReadonlyArray<ModelRef>,
|
||||
structures: [] as ReadonlyArray<StructureRef>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class StructureHierarchyManager extends PluginComponent<StructureHierarchyManagerState> {
|
||||
readonly behaviors = {
|
||||
selection: this.ev.behavior({
|
||||
hierarchy: this.state.hierarchy,
|
||||
trajectories: this.state.selection.trajectories,
|
||||
models: this.state.selection.models,
|
||||
structures: this.state.selection.structures
|
||||
hierarchy: this.current,
|
||||
trajectories: this.selection.trajectories,
|
||||
models: this.selection.models,
|
||||
structures: this.selection.structures
|
||||
})
|
||||
}
|
||||
|
||||
@@ -38,7 +43,7 @@ export class StructureHierarchyManager extends PluginComponent<StructureHierarch
|
||||
|
||||
get currentComponentGroups() {
|
||||
if (this._currentComponentGroups) return this._currentComponentGroups;
|
||||
this._currentComponentGroups = StructureHierarchyManager.getComponentGroups(this.state.selection.structures);
|
||||
this._currentComponentGroups = StructureHierarchyManager.getComponentGroups(this.selection.structures);
|
||||
return this._currentComponentGroups;
|
||||
}
|
||||
|
||||
@@ -46,17 +51,19 @@ export class StructureHierarchyManager extends PluginComponent<StructureHierarch
|
||||
get seletionSet() {
|
||||
if (this._currentSelectionSet) return this._currentSelectionSet;
|
||||
this._currentSelectionSet = new Set();
|
||||
for (const r of this.state.selection.trajectories) this._currentSelectionSet.add(r.cell.transform.ref);
|
||||
for (const r of this.state.selection.models) this._currentSelectionSet.add(r.cell.transform.ref);
|
||||
for (const r of this.state.selection.structures) this._currentSelectionSet.add(r.cell.transform.ref);
|
||||
for (const r of this.selection.trajectories) this._currentSelectionSet.add(r.cell.transform.ref);
|
||||
for (const r of this.selection.models) this._currentSelectionSet.add(r.cell.transform.ref);
|
||||
for (const r of this.selection.structures) this._currentSelectionSet.add(r.cell.transform.ref);
|
||||
return this._currentSelectionSet;
|
||||
}
|
||||
|
||||
get current() {
|
||||
this.sync();
|
||||
return this.state.hierarchy;
|
||||
}
|
||||
|
||||
get selection() {
|
||||
this.sync();
|
||||
return this.state.selection;
|
||||
}
|
||||
|
||||
@@ -74,7 +81,11 @@ export class StructureHierarchyManager extends PluginComponent<StructureHierarch
|
||||
}
|
||||
|
||||
private sync() {
|
||||
const update = buildStructureHierarchy(this.plugin.state.data, this.state.hierarchy);
|
||||
if (this.state.syncedTree === this.dataState.tree) return;
|
||||
|
||||
this.state.syncedTree = this.dataState.tree;
|
||||
|
||||
const update = buildStructureHierarchy(this.plugin.state.data, this.current);
|
||||
if (!update.changed) {
|
||||
return;
|
||||
}
|
||||
@@ -87,12 +98,16 @@ export class StructureHierarchyManager extends PluginComponent<StructureHierarch
|
||||
this._currentComponentGroups = void 0;
|
||||
this._currentSelectionSet = void 0;
|
||||
|
||||
this.updateState({ hierarchy, selection: { trajectories, models, structures } });
|
||||
this.state.hierarchy = hierarchy;
|
||||
this.state.selection.trajectories = trajectories;
|
||||
this.state.selection.models = models;
|
||||
this.state.selection.structures = structures;
|
||||
|
||||
this.behaviors.selection.next({ hierarchy, trajectories, models, structures });
|
||||
}
|
||||
|
||||
updateCurrent(refs: HierarchyRef[], action: 'add' | 'remove') {
|
||||
const hierarchy = this.state.hierarchy;
|
||||
const hierarchy = this.current;
|
||||
const set = action === 'add'
|
||||
? SetUtils.union(this.seletionSet, new Set(refs.map(r => r.cell.transform.ref)))
|
||||
: SetUtils.difference(this.seletionSet, new Set(refs.map(r => r.cell.transform.ref)));
|
||||
@@ -103,47 +118,64 @@ export class StructureHierarchyManager extends PluginComponent<StructureHierarch
|
||||
|
||||
for (const t of hierarchy.trajectories) {
|
||||
if (set.has(t.cell.transform.ref)) trajectories.push(t);
|
||||
for (const m of t.models) {
|
||||
if (set.has(m.cell.transform.ref)) models.push(m);
|
||||
for (const s of m.structures) {
|
||||
if (set.has(s.cell.transform.ref)) structures.push(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const m of hierarchy.models) {
|
||||
if (set.has(m.cell.transform.ref)) models.push(m);
|
||||
}
|
||||
for (const s of hierarchy.structures) {
|
||||
if (set.has(s.cell.transform.ref)) structures.push(s);
|
||||
}
|
||||
|
||||
this._currentComponentGroups = void 0;
|
||||
this._currentSelectionSet = void 0;
|
||||
|
||||
this.updateState({ selection: { trajectories, models, structures } });
|
||||
this.state.selection.trajectories = trajectories;
|
||||
this.state.selection.models = models;
|
||||
this.state.selection.structures = structures;
|
||||
|
||||
this.behaviors.selection.next({ hierarchy, trajectories, models, structures });
|
||||
}
|
||||
|
||||
remove(refs: HierarchyRef[], canUndo?: boolean) {
|
||||
remove(refs: (HierarchyRef | string)[], canUndo?: boolean) {
|
||||
if (refs.length === 0) return;
|
||||
const deletes = this.plugin.state.data.build();
|
||||
for (const r of refs) deletes.delete(r.cell.transform.ref);
|
||||
for (const r of refs) deletes.delete(typeof r === 'string' ? r : r.cell.transform.ref);
|
||||
return this.plugin.updateDataState(deletes, { canUndo: canUndo ? 'Remove' : false });
|
||||
}
|
||||
|
||||
createModels(trajectories: ReadonlyArray<TrajectoryRef>, kind: 'single' | 'all' = 'single') {
|
||||
toggleVisibility(refs: ReadonlyArray<HierarchyRef>, action?: 'show' | 'hide') {
|
||||
if (refs.length === 0) return;
|
||||
|
||||
const isHidden = action !== void 0
|
||||
? (action === 'show' ? false : true)
|
||||
: !refs[0].cell.state.isHidden;
|
||||
for (const c of refs) {
|
||||
setSubtreeVisibility(this.dataState, c.cell.transform.ref, isHidden);
|
||||
}
|
||||
}
|
||||
|
||||
applyPreset<P = any, S = {}>(trajectories: ReadonlyArray<TrajectoryRef>, provider: TrajectoryHierarchyPresetProvider<P, S>, params?: P): Promise<any> {
|
||||
return this.plugin.dataTransaction(async () => {
|
||||
for (const trajectory of trajectories) {
|
||||
if (trajectory.models.length > 0) {
|
||||
await this.clearTrajectory(trajectory);
|
||||
}
|
||||
|
||||
if (trajectory.models.length === 0) return;
|
||||
|
||||
const tr = trajectory.cell.obj?.data!;
|
||||
if (kind === 'all' && tr.length > 1) {
|
||||
await applyTrajectoryHierarchyPreset(this.plugin, trajectory.cell, 'all-models');
|
||||
} else {
|
||||
await applyTrajectoryHierarchyPreset(this.plugin, trajectory.cell, 'first-model');
|
||||
for (const t of trajectories) {
|
||||
if (t.models.length > 0) {
|
||||
await this.clearTrajectory(t);
|
||||
}
|
||||
await this.plugin.builders.structure.hierarchy.applyPreset(t.cell, provider, params);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async updateStructure(s: StructureRef, params: any) {
|
||||
await this.plugin.dataTransaction(async () => {
|
||||
const root = StateTree.getDecoratorRoot(this.dataState.tree, s.cell.transform.ref);
|
||||
const children = this.dataState.tree.children.get(root).toArray();
|
||||
await this.remove(children, false);
|
||||
await this.plugin.state.updateTransform(this.plugin.state.data, s.cell.transform.ref, params, 'Structure Type');
|
||||
await this.plugin.builders.structure.representation.applyPreset(s.cell.transform.ref, 'auto');
|
||||
}, { canUndo: 'Structure Type' });
|
||||
PluginCommands.Camera.Reset(this.plugin);
|
||||
}
|
||||
|
||||
private clearTrajectory(trajectory: TrajectoryRef) {
|
||||
const builder = this.dataState.build();
|
||||
for (const m of trajectory.models) {
|
||||
@@ -153,17 +185,14 @@ export class StructureHierarchyManager extends PluginComponent<StructureHierarch
|
||||
}
|
||||
|
||||
constructor(private plugin: PluginContext) {
|
||||
super({
|
||||
hierarchy: StructureHierarchy(),
|
||||
selection: { trajectories: [], models: [], structures: [] }
|
||||
});
|
||||
super();
|
||||
|
||||
plugin.state.data.events.changed.subscribe(e => {
|
||||
this.subscribe(plugin.state.data.events.changed, e => {
|
||||
if (e.inTransaction || plugin.behaviors.state.isAnimating.value) return;
|
||||
this.sync();
|
||||
});
|
||||
|
||||
plugin.behaviors.state.isAnimating.subscribe(isAnimating => {
|
||||
this.subscribe(plugin.behaviors.state.isAnimating, isAnimating => {
|
||||
if (!isAnimating && !plugin.behaviors.state.isUpdating.value) this.sync();
|
||||
});
|
||||
}
|
||||
@@ -196,12 +225,19 @@ export namespace StructureHierarchyManager {
|
||||
}
|
||||
|
||||
export function getSelectedStructuresDescription(plugin: PluginContext) {
|
||||
const { structures } = plugin.managers.structure.hierarchy.state.selection;
|
||||
const { structures } = plugin.managers.structure.hierarchy.selection;
|
||||
if (structures.length === 0) return '';
|
||||
|
||||
if (structures.length === 1) {
|
||||
const s = structures[0];
|
||||
const entryId = s.cell.obj?.data.models[0].entryId;
|
||||
const data = s.cell.obj?.data;
|
||||
|
||||
if (!data) return s.cell.obj?.label || 'Structure';
|
||||
|
||||
const model = data.models[0] || data.representativeModel || data.masterModel;
|
||||
if (!model) return s.cell.obj?.label || 'Structure';
|
||||
|
||||
const entryId = model.entryId;
|
||||
if (s.model?.trajectory?.models && s.model.trajectory.models.length === 1) return entryId;
|
||||
if (s.model) return `${s.model.cell.obj?.label} | ${entryId}`;
|
||||
return entryId;
|
||||
|
||||
@@ -11,7 +11,7 @@ import { StateTransforms } from '../../transforms';
|
||||
import { PluginCommands } from '../../../mol-plugin/commands';
|
||||
import { arraySetAdd } from '../../../mol-util/array';
|
||||
import { PluginStateObject } from '../../objects';
|
||||
import { PluginComponent } from '../../component';
|
||||
import { StatefulPluginComponent } from '../../component';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { MeasurementRepresentationCommonTextParams } from '../../../mol-repr/shape/loci/common';
|
||||
|
||||
@@ -37,7 +37,7 @@ export interface StructureMeasurementManagerState {
|
||||
options: StructureMeasurementOptions
|
||||
}
|
||||
|
||||
class StructureMeasurementManager extends PluginComponent<StructureMeasurementManagerState> {
|
||||
class StructureMeasurementManager extends StatefulPluginComponent<StructureMeasurementManagerState> {
|
||||
readonly behaviors = {
|
||||
state: this.ev.behavior(this.state)
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ import { StateObject, StateObjectRef } from '../../../mol-state';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { structureElementStatsLabel } from '../../../mol-theme/label';
|
||||
import { arrayRemoveAtInPlace } from '../../../mol-util/array';
|
||||
import { PluginComponent } from '../../component';
|
||||
import { StatefulPluginComponent } from '../../component';
|
||||
import { StructureSelectionQuery } from '../../helpers/structure-selection-query';
|
||||
import { PluginStateObject } from '../../objects';
|
||||
import { UUID } from '../../../mol-util';
|
||||
@@ -33,7 +33,7 @@ const HISTORY_CAPACITY = 8;
|
||||
|
||||
export type StructureSelectionModifier = 'add' | 'remove' | 'intersect' | 'set'
|
||||
|
||||
export class StructureSelectionManager extends PluginComponent<StructureSelectionManagerState> {
|
||||
export class StructureSelectionManager extends StatefulPluginComponent<StructureSelectionManagerState> {
|
||||
readonly events = {
|
||||
changed: this.ev<undefined>(),
|
||||
additionsHistoryUpdated: this.ev<undefined>()
|
||||
@@ -173,6 +173,7 @@ export class StructureSelectionManager extends PluginComponent<StructureSelectio
|
||||
}
|
||||
|
||||
if (entry) {
|
||||
// move to top
|
||||
arrayRemoveAtInPlace(this.additionsHistory, idx);
|
||||
this.additionsHistory.unshift(entry);
|
||||
this.events.additionsHistoryUpdated.next();
|
||||
@@ -188,23 +189,6 @@ export class StructureSelectionManager extends PluginComponent<StructureSelectio
|
||||
this.events.additionsHistoryUpdated.next();
|
||||
}
|
||||
|
||||
// private removeHistory(loci: Loci) {
|
||||
// if (Loci.isEmpty(loci)) return;
|
||||
|
||||
// let idx = 0, found = false;
|
||||
// for (const l of this.history) {
|
||||
// if (Loci.areEqual(l.loci, loci)) {
|
||||
// found = true;
|
||||
// break;
|
||||
// }
|
||||
// idx++;
|
||||
// }
|
||||
|
||||
// if (found) {
|
||||
// arrayRemoveAtInPlace(this.history, idx);
|
||||
// }
|
||||
// }
|
||||
|
||||
private onRemove(ref: string) {
|
||||
if (this.entries.has(ref)) {
|
||||
this.entries.delete(ref);
|
||||
@@ -246,9 +230,9 @@ export class StructureSelectionManager extends PluginComponent<StructureSelectio
|
||||
if (!StructureElement.Loci.isEmpty(s.selection)) selections.push(s.selection);
|
||||
s.selection = StructureElement.Loci(s.selection.structure, []);
|
||||
}
|
||||
this.referenceLoci = undefined
|
||||
this.referenceLoci = undefined;
|
||||
this.state.stats = void 0;
|
||||
this.events.changed.next()
|
||||
this.events.changed.next();
|
||||
return selections;
|
||||
}
|
||||
|
||||
@@ -346,12 +330,12 @@ export class StructureSelectionManager extends PluginComponent<StructureSelectio
|
||||
const { box, sphere } = boundaries[i];
|
||||
Vec3.min(min, min, box.min);
|
||||
Vec3.max(max, max, box.max);
|
||||
boundaryHelper.includeSphereStep(sphere.center, sphere.radius)
|
||||
boundaryHelper.includePositionRadius(sphere.center, sphere.radius)
|
||||
}
|
||||
boundaryHelper.finishedIncludeStep();
|
||||
for (let i = 0, il = boundaries.length; i < il; ++i) {
|
||||
const { sphere } = boundaries[i];
|
||||
boundaryHelper.radiusSphereStep(sphere.center, sphere.radius);
|
||||
boundaryHelper.radiusPositionRadius(sphere.center, sphere.radius);
|
||||
}
|
||||
|
||||
return { box: { min, max }, sphere: boundaryHelper.getSphere() };
|
||||
@@ -384,7 +368,7 @@ export class StructureSelectionManager extends PluginComponent<StructureSelectio
|
||||
}
|
||||
|
||||
private get applicableStructures() {
|
||||
return this.plugin.managers.structure.hierarchy.state.selection.structures
|
||||
return this.plugin.managers.structure.hierarchy.selection.structures
|
||||
.filter(s => !!s.cell.obj)
|
||||
.map(s => s.cell.obj!.data);
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
import { List } from 'immutable';
|
||||
import { UUID } from '../mol-util';
|
||||
import { PluginState } from '../mol-plugin/state';
|
||||
import { PluginComponent } from './component';
|
||||
import { StatefulPluginComponent } from './component';
|
||||
import { PluginContext } from '../mol-plugin/context';
|
||||
|
||||
export { PluginStateSnapshotManager }
|
||||
|
||||
class PluginStateSnapshotManager extends PluginComponent<{
|
||||
class PluginStateSnapshotManager extends StatefulPluginComponent<{
|
||||
current?: UUID | undefined,
|
||||
entries: List<PluginStateSnapshotManager.Entry>,
|
||||
isPlaying: boolean,
|
||||
|
||||
@@ -5,33 +5,33 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { parse3DG } from '../../mol-io/reader/3dg/parser';
|
||||
import { parseDcd } from '../../mol-io/reader/dcd/parser';
|
||||
import { parseGRO } from '../../mol-io/reader/gro/parser';
|
||||
import { parsePDB } from '../../mol-io/reader/pdb/parser';
|
||||
import { Vec3, Mat4, Quat } from '../../mol-math/linear-algebra';
|
||||
import { SymmetryOperator } from '../../mol-math/geometry';
|
||||
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { shapeFromPly } from '../../mol-model-formats/shape/ply';
|
||||
import { trajectoryFrom3DG } from '../../mol-model-formats/structure/3dg';
|
||||
import { coordinatesFromDcd } from '../../mol-model-formats/structure/dcd';
|
||||
import { trajectoryFromGRO } from '../../mol-model-formats/structure/gro';
|
||||
import { trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif';
|
||||
import { trajectoryFromPDB } from '../../mol-model-formats/structure/pdb';
|
||||
import { Model, Queries, QueryContext, Structure, StructureQuery, StructureSelection as Sel, StructureElement, Coordinates, Topology } from '../../mol-model/structure';
|
||||
import { topologyFromPsf } from '../../mol-model-formats/structure/psf';
|
||||
import { Coordinates, Model, Queries, QueryContext, Structure, StructureElement, StructureQuery, StructureSelection as Sel, Topology } from '../../mol-model/structure';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { MolScriptBuilder } from '../../mol-script/language/builder';
|
||||
import Expression from '../../mol-script/language/expression';
|
||||
import { Script } from '../../mol-script/script';
|
||||
import { StateObject, StateTransformer } from '../../mol-state';
|
||||
import { RuntimeContext, Task } from '../../mol-task';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { PluginStateObject as SO, PluginStateTransform } from '../objects';
|
||||
import { trajectoryFromGRO } from '../../mol-model-formats/structure/gro';
|
||||
import { parseGRO } from '../../mol-io/reader/gro/parser';
|
||||
import { shapeFromPly } from '../../mol-model-formats/shape/ply';
|
||||
import { SymmetryOperator } from '../../mol-math/geometry';
|
||||
import { Script } from '../../mol-script/script';
|
||||
import { parse3DG } from '../../mol-io/reader/3dg/parser';
|
||||
import { trajectoryFrom3DG } from '../../mol-model-formats/structure/3dg';
|
||||
import { StructureSelectionQueries } from '../helpers/structure-selection-query';
|
||||
import { StructureQueryHelper } from '../helpers/structure-query';
|
||||
import { RootStructureDefinition } from '../helpers/root-structure';
|
||||
import { parseDcd } from '../../mol-io/reader/dcd/parser';
|
||||
import { coordinatesFromDcd } from '../../mol-model-formats/structure/dcd';
|
||||
import { topologyFromPsf } from '../../mol-model-formats/structure/psf';
|
||||
import { deepEqual } from '../../mol-util';
|
||||
import { StructureComponentParams, createStructureComponent, updateStructureComponent } from '../helpers/structure-component';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { RootStructureDefinition } from '../helpers/root-structure';
|
||||
import { createStructureComponent, StructureComponentParams, updateStructureComponent } from '../helpers/structure-component';
|
||||
import { StructureQueryHelper } from '../helpers/structure-query';
|
||||
import { StructureSelectionQueries } from '../helpers/structure-selection-query';
|
||||
import { PluginStateObject as SO, PluginStateTransform } from '../objects';
|
||||
|
||||
export { CoordinatesFromDcd };
|
||||
export { TopologyFromPsf };
|
||||
@@ -44,16 +44,17 @@ export { TrajectoryFrom3DG };
|
||||
export { ModelFromTrajectory };
|
||||
export { StructureFromTrajectory };
|
||||
export { StructureFromModel };
|
||||
export { StructureCoordinateSystem };
|
||||
export { TransformStructureConformation };
|
||||
export { TransformStructureConformationByMatrix };
|
||||
export { StructureSelectionFromExpression };
|
||||
export { MultiStructureSelectionFromExpression }
|
||||
export { MultiStructureSelectionFromExpression };
|
||||
export { StructureSelectionFromScript };
|
||||
export { StructureSelectionFromBundle };
|
||||
export { StructureComplexElement };
|
||||
export { StructureComponent }
|
||||
export { StructureComponent };
|
||||
export { CustomModelProperties };
|
||||
export { CustomStructureProperties };
|
||||
export { ShapeFromPly };
|
||||
|
||||
type CoordinatesFromDcd = typeof CoordinatesFromDcd
|
||||
const CoordinatesFromDcd = PluginStateTransform.BuiltIn({
|
||||
@@ -278,6 +279,9 @@ const StructureFromModel = PluginStateTransform.BuiltIn({
|
||||
to: SO.Molecule.Structure,
|
||||
params(a) { return RootStructureDefinition.getParams(a && a.data); }
|
||||
})({
|
||||
canAutoUpdate({ oldParams, newParams }) {
|
||||
return RootStructureDefinition.canAutoUpdate(oldParams.type, newParams.type);
|
||||
},
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Build Structure', async ctx => {
|
||||
return RootStructureDefinition.create(plugin, ctx, a.data, params && params.type);
|
||||
@@ -291,16 +295,66 @@ const StructureFromModel = PluginStateTransform.BuiltIn({
|
||||
});
|
||||
|
||||
const _translation = Vec3(), _m = Mat4(), _n = Mat4();
|
||||
|
||||
type StructureCoordinateSystem = typeof StructureCoordinateSystem
|
||||
const StructureCoordinateSystem = PluginStateTransform.BuiltIn({
|
||||
name: 'structure-coordinate-system',
|
||||
display: { name: 'Coordinate System' },
|
||||
isDecorator: true,
|
||||
from: SO.Molecule.Structure,
|
||||
to: SO.Molecule.Structure,
|
||||
params: {
|
||||
transform: PD.MappedStatic('components', {
|
||||
components: PD.Group({
|
||||
axis: PD.Vec3(Vec3.create(1, 0, 0)),
|
||||
angle: PD.Numeric(0, { min: -180, max: 180, step: 0.1 }),
|
||||
translation: PD.Vec3(Vec3.create(0, 0, 0)),
|
||||
}, { isFlat: true }),
|
||||
matrix: PD.Value(Mat4.identity(), { isHidden: true })
|
||||
}, { label: 'Kind' })
|
||||
}
|
||||
})({
|
||||
canAutoUpdate({ newParams }) {
|
||||
return newParams.transform.name === 'components';
|
||||
},
|
||||
apply({ a, params }) {
|
||||
// TODO: optimze
|
||||
|
||||
const transform = Mat4.zero();
|
||||
|
||||
if (params.transform.name === 'components') {
|
||||
const { axis, angle, translation } = params.transform.params;
|
||||
const center = a.data.boundary.sphere.center;
|
||||
Mat4.fromTranslation(_m, Vec3.negate(_translation, center));
|
||||
Mat4.fromTranslation(_n, Vec3.add(_translation, center, translation));
|
||||
const rot = Mat4.fromRotation(Mat4.zero(), Math.PI / 180 * angle, Vec3.normalize(Vec3.zero(), axis));
|
||||
Mat4.mul3(transform, _n, rot, _m);
|
||||
} else {
|
||||
Mat4.copy(transform, params.transform.params);
|
||||
}
|
||||
|
||||
// TODO: compose with parent's coordinate system
|
||||
a.data.coordinateSystem = SymmetryOperator.create('CS', transform);
|
||||
return new SO.Molecule.Structure(a.data, { label: a.label, description: `${a.description} [Transformed]` });
|
||||
}
|
||||
});
|
||||
|
||||
type TransformStructureConformation = typeof TransformStructureConformation
|
||||
const TransformStructureConformation = PluginStateTransform.BuiltIn({
|
||||
name: 'transform-structure-conformation',
|
||||
display: { name: 'Transform Conformation' },
|
||||
isDecorator: true,
|
||||
from: SO.Molecule.Structure,
|
||||
to: SO.Molecule.Structure,
|
||||
params: {
|
||||
axis: PD.Vec3(Vec3.create(1, 0, 0)),
|
||||
angle: PD.Numeric(0, { min: -180, max: 180, step: 0.1 }),
|
||||
translation: PD.Vec3(Vec3.create(0, 0, 0)),
|
||||
transform: PD.MappedStatic('components', {
|
||||
components: PD.Group({
|
||||
axis: PD.Vec3(Vec3.create(1, 0, 0)),
|
||||
angle: PD.Numeric(0, { min: -180, max: 180, step: 0.1 }),
|
||||
translation: PD.Vec3(Vec3.create(0, 0, 0)),
|
||||
}, { isFlat: true }),
|
||||
matrix: PD.Value(Mat4.identity(), { isHidden: true })
|
||||
}, { label: 'Kind' })
|
||||
}
|
||||
})({
|
||||
canAutoUpdate() {
|
||||
@@ -309,51 +363,35 @@ const TransformStructureConformation = PluginStateTransform.BuiltIn({
|
||||
apply({ a, params }) {
|
||||
// TODO: optimze
|
||||
|
||||
const center = a.data.boundary.sphere.center;
|
||||
Mat4.fromTranslation(_m, Vec3.negate(_translation, center));
|
||||
Mat4.fromTranslation(_n, Vec3.add(_translation, center, params.translation));
|
||||
const rot = Mat4.fromRotation(Mat4.zero(), Math.PI / 180 * params.angle, Vec3.normalize(Vec3.zero(), params.axis));
|
||||
const transform = Mat4.zero();
|
||||
|
||||
const m = Mat4.zero();
|
||||
Mat4.mul3(m, _n, rot, _m);
|
||||
if (params.transform.name === 'components') {
|
||||
const { axis, angle, translation } = params.transform.params;
|
||||
const center = a.data.boundary.sphere.center;
|
||||
Mat4.fromTranslation(_m, Vec3.negate(_translation, center));
|
||||
Mat4.fromTranslation(_n, Vec3.add(_translation, center, translation));
|
||||
const rot = Mat4.fromRotation(Mat4.zero(), Math.PI / 180 * angle, Vec3.normalize(Vec3.zero(), axis));
|
||||
Mat4.mul3(transform, _n, rot, _m);
|
||||
} else {
|
||||
Mat4.copy(transform, params.transform.params);
|
||||
}
|
||||
|
||||
const s = Structure.transform(a.data, m);
|
||||
const props = { label: `${a.label}`, description: 'Transformed' };
|
||||
return new SO.Molecule.Structure(s, props);
|
||||
},
|
||||
interpolate(src, tar, t) {
|
||||
// TODO: optimize
|
||||
const u = Mat4.fromRotation(Mat4.zero(), Math.PI / 180 * src.angle, Vec3.normalize(Vec3(), src.axis));
|
||||
Mat4.setTranslation(u, src.translation);
|
||||
const v = Mat4.fromRotation(Mat4.zero(), Math.PI / 180 * tar.angle, Vec3.normalize(Vec3(), tar.axis));
|
||||
Mat4.setTranslation(v, tar.translation);
|
||||
const m = SymmetryOperator.slerp(Mat4.zero(), u, v, t);
|
||||
const rot = Mat4.getRotation(Quat.zero(), m);
|
||||
const axis = Vec3.zero();
|
||||
const angle = Quat.getAxisAngle(axis, rot);
|
||||
const translation = Mat4.getTranslation(Vec3.zero(), m);
|
||||
return { axis, angle, translation };
|
||||
}
|
||||
});
|
||||
|
||||
type TransformStructureConformationByMatrix = typeof TransformStructureConformation
|
||||
const TransformStructureConformationByMatrix = PluginStateTransform.BuiltIn({
|
||||
name: 'transform-structure-conformation-by-matrix',
|
||||
display: { name: 'Transform Conformation' },
|
||||
from: SO.Molecule.Structure,
|
||||
to: SO.Molecule.Structure,
|
||||
params: {
|
||||
matrix: PD.Value<Mat4>(Mat4.identity(), { isHidden: true })
|
||||
}
|
||||
})({
|
||||
canAutoUpdate() {
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }) {
|
||||
const s = Structure.transform(a.data, params.matrix);
|
||||
const props = { label: `${a.label}`, description: 'Transformed' };
|
||||
return new SO.Molecule.Structure(s, props);
|
||||
const s = Structure.transform(a.data, transform);
|
||||
return new SO.Molecule.Structure(s, { label: a.label, description: `${a.description} [Transformed]` });
|
||||
}
|
||||
// interpolate(src, tar, t) {
|
||||
// // TODO: optimize
|
||||
// const u = Mat4.fromRotation(Mat4.zero(), Math.PI / 180 * src.angle, Vec3.normalize(Vec3(), src.axis));
|
||||
// Mat4.setTranslation(u, src.translation);
|
||||
// const v = Mat4.fromRotation(Mat4.zero(), Math.PI / 180 * tar.angle, Vec3.normalize(Vec3(), tar.axis));
|
||||
// Mat4.setTranslation(v, tar.translation);
|
||||
// const m = SymmetryOperator.slerp(Mat4.zero(), u, v, t);
|
||||
// const rot = Mat4.getRotation(Quat.zero(), m);
|
||||
// const axis = Vec3.zero();
|
||||
// const angle = Quat.getAxisAngle(axis, rot);
|
||||
// const translation = Mat4.getTranslation(Vec3.zero(), m);
|
||||
// return { axis, angle, translation };
|
||||
// }
|
||||
});
|
||||
|
||||
type StructureSelectionFromExpression = typeof StructureSelectionFromExpression
|
||||
@@ -685,6 +723,7 @@ type CustomModelProperties = typeof CustomModelProperties
|
||||
const CustomModelProperties = PluginStateTransform.BuiltIn({
|
||||
name: 'custom-model-properties',
|
||||
display: { name: 'Custom Model Properties' },
|
||||
isDecorator: true,
|
||||
from: SO.Molecule.Model,
|
||||
to: SO.Molecule.Model,
|
||||
params: (a, ctx: PluginContext) => {
|
||||
@@ -734,6 +773,7 @@ type CustomStructureProperties = typeof CustomStructureProperties
|
||||
const CustomStructureProperties = PluginStateTransform.BuiltIn({
|
||||
name: 'custom-structure-properties',
|
||||
display: { name: 'Custom Structure Properties' },
|
||||
isDecorator: true,
|
||||
from: SO.Molecule.Structure,
|
||||
to: SO.Molecule.Structure,
|
||||
params: (a, ctx: PluginContext) => {
|
||||
@@ -779,7 +819,6 @@ async function attachStructureProps(structure: Structure, ctx: PluginContext, ta
|
||||
}
|
||||
}
|
||||
|
||||
export { ShapeFromPly }
|
||||
type ShapeFromPly = typeof ShapeFromPly
|
||||
const ShapeFromPly = PluginStateTransform.BuiltIn({
|
||||
name: 'shape-from-ply',
|
||||
|
||||
@@ -118,7 +118,7 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
oldParams.type.name === newParams.type.name && newParams.type.params.quality !== 'custom'
|
||||
);
|
||||
},
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
apply({ a, params, cache }, plugin: PluginContext) {
|
||||
return Task.create('Structure Representation', async ctx => {
|
||||
const propertyCtx = { runtime: ctx, fetch: plugin.fetch }
|
||||
const provider = plugin.representation.structure.registry.get(params.type.name)
|
||||
@@ -127,29 +127,52 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, provider.getParams)
|
||||
await Theme.ensureDependencies(propertyCtx, plugin.representation.structure.themes, { structure: a.data }, params)
|
||||
repr.setTheme(Theme.create(plugin.representation.structure.themes, { structure: a.data }, params))
|
||||
|
||||
// TODO: build this into representation?
|
||||
if (!a.data.coordinateSystem.isIdentity) {
|
||||
(cache as any)['transform'] = a.data.coordinateSystem;
|
||||
repr.setState({ transform: a.data.coordinateSystem.matrix });
|
||||
}
|
||||
|
||||
// TODO set initial state, repr.setState({})
|
||||
await repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
return new SO.Molecule.Structure.Representation3D({ repr, source: a } , { label: provider.label });
|
||||
return new SO.Molecule.Structure.Representation3D({ repr, source: a }, { label: provider.label });
|
||||
});
|
||||
},
|
||||
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
|
||||
update({ a, b, oldParams, newParams, cache }, plugin: PluginContext) {
|
||||
return Task.create('Structure Representation', async ctx => {
|
||||
const oldProvider = plugin.representation.structure.registry.get(oldParams.type.name);
|
||||
const propertyCtx = { runtime: ctx, fetch: plugin.fetch }
|
||||
if (oldProvider.ensureCustomProperties) oldProvider.ensureCustomProperties.detach(propertyCtx, a.data);
|
||||
Theme.releaseDependencies(propertyCtx, plugin.representation.structure.themes, { structure: a.data }, oldParams);
|
||||
if (oldProvider.ensureCustomProperties) oldProvider.ensureCustomProperties.detach(a.data);
|
||||
Theme.releaseDependencies(plugin.representation.structure.themes, { structure: a.data }, oldParams);
|
||||
|
||||
if (newParams.type.name !== oldParams.type.name) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const provider = plugin.representation.structure.registry.get(newParams.type.name)
|
||||
const propertyCtx = { runtime: ctx, fetch: plugin.fetch }
|
||||
if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data)
|
||||
const props = { ...b.data.repr.props, ...newParams.type.params }
|
||||
await Theme.ensureDependencies(propertyCtx, plugin.representation.structure.themes, { structure: a.data }, newParams)
|
||||
b.data.repr.setTheme(Theme.create(plugin.representation.structure.themes, { structure: a.data }, newParams));
|
||||
|
||||
// TODO: build this into representation?
|
||||
if ((cache as any)['transform'] !== a.data.coordinateSystem) {
|
||||
(cache as any)['transform'] = a.data.coordinateSystem;
|
||||
b.data.repr.setState({ transform: a.data.coordinateSystem.matrix });
|
||||
}
|
||||
|
||||
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
b.data.source = a
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
},
|
||||
dispose({ b, params }, plugin: PluginContext) {
|
||||
if (!b || !params) return;
|
||||
|
||||
const structure = b.data.source.data;
|
||||
const provider = plugin.representation.structure.registry.get(params.type.name);
|
||||
if (provider.ensureCustomProperties) provider.ensureCustomProperties.detach(structure);
|
||||
Theme.releaseDependencies(plugin.representation.structure.themes, { structure }, params);
|
||||
},
|
||||
interpolate(src, tar, t) {
|
||||
if (src.colorTheme.name !== 'uniform' || tar.colorTheme.name !== 'uniform') {
|
||||
return t <= 0.5 ? src : tar;
|
||||
@@ -461,7 +484,7 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
type: PD.Mapped<any>(
|
||||
registry.default.name,
|
||||
registry.types,
|
||||
name => PD.Group<any>(registry.get(name).getParams(themeCtx, VolumeData.One ))),
|
||||
name => PD.Group<any>(registry.get(name).getParams(themeCtx, VolumeData.One))),
|
||||
colorTheme: PD.Mapped<any>(
|
||||
type.defaultColorTheme.name,
|
||||
themeCtx.colorThemeRegistry.types,
|
||||
@@ -516,8 +539,7 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
if (newParams.type.name !== oldParams.type.name) {
|
||||
const oldProvider = plugin.representation.volume.registry.get(oldParams.type.name);
|
||||
if (oldProvider.ensureCustomProperties) {
|
||||
const propertyCtx = { runtime: ctx, fetch: plugin.fetch }
|
||||
oldProvider.ensureCustomProperties.detach(propertyCtx, a.data)
|
||||
oldProvider.ensureCustomProperties.detach(a.data)
|
||||
}
|
||||
return StateTransformer.UpdateResult.Recreate;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import * as React from 'react';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { PluginContext } from '../mol-plugin/context';
|
||||
import { Icon } from './controls/icons';
|
||||
import { Button } from './controls/common';
|
||||
|
||||
export const PluginReactContext = React.createContext(void 0 as any as PluginContext);
|
||||
|
||||
@@ -89,11 +89,10 @@ export abstract class CollapsableControls<P = {}, S = {}, SS = {}> extends Plugi
|
||||
|
||||
return <div className={wrapClass}>
|
||||
<div className='msp-transform-header'>
|
||||
<button className='msp-btn msp-btn-block msp-btn-collapse msp-no-overflow' onClick={this.toggleCollapsed}>
|
||||
<Icon name={this.state.isCollapsed ? 'expand' : 'collapse'} />
|
||||
<Button icon={this.state.isCollapsed ? 'expand' : 'collapse'} noOverflow onClick={this.toggleCollapsed}>
|
||||
{this.state.header}
|
||||
<small style={{ margin: '0 6px' }}>{this.state.isCollapsed ? '' : this.state.description}</small>
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
{!this.state.isCollapsed && this.renderControls()}
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { PluginUIComponent } from './base';
|
||||
import { ParamDefinition as PD } from '../mol-util/param-definition';
|
||||
import { ParameterControls } from './controls/parameters';
|
||||
import { Icon } from './controls/icons';
|
||||
import { Button, IconButton } from './controls/common';
|
||||
|
||||
export class CameraSnapshots extends PluginUIComponent<{ }, { }> {
|
||||
render() {
|
||||
@@ -42,8 +43,8 @@ class CameraSnapshotControls extends PluginUIComponent<{ }, { name: string, desc
|
||||
<ParameterControls params={CameraSnapshotControls.Params} values={this.state} onEnter={this.add} onChange={p => this.setState({ [p.name]: p.value } as any)} />
|
||||
|
||||
<div className='msp-btn-row-group'>
|
||||
<button className='msp-btn msp-btn-block msp-form-control' onClick={this.add}>Add</button>
|
||||
<button className='msp-btn msp-btn-block msp-form-control' onClick={this.clear}>Clear</button>
|
||||
<Button onClick={this.add}>Add</Button>
|
||||
<Button onClick={this.clear}>Clear</Button>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
@@ -67,10 +68,8 @@ class CameraSnapshotList extends PluginUIComponent<{ }, { }> {
|
||||
render() {
|
||||
return <ul style={{ listStyle: 'none' }} className='msp-state-list'>
|
||||
{this.plugin.state.cameraSnapshots.state.entries.valueSeq().map(e =><li key={e!.id}>
|
||||
<button className='msp-btn msp-btn-block msp-form-control' onClick={this.apply(e!.id)}>{e!.name || e!.timestamp} <small>{e!.description}</small></button>
|
||||
<button onClick={this.remove(e!.id)} className='msp-btn msp-btn-link msp-state-list-remove-button'>
|
||||
<Icon name='remove' />
|
||||
</button>
|
||||
<Button onClick={this.apply(e!.id)}>{e!.name || e!.timestamp} <small>{e!.description}</small></Button>
|
||||
<IconButton icon='remove' onClick={this.remove(e!.id)} className='msp-state-list-remove-button' />
|
||||
</li>)}
|
||||
</ul>;
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@ import { StateTransformer } from '../mol-state';
|
||||
import { ModelFromTrajectory } from '../mol-plugin-state/transforms/model';
|
||||
import { AnimationControls } from './state/animation';
|
||||
import { StructureSelectionControls } from './structure/selection';
|
||||
import { StructureMeasurementsControls } from './structure/measurements';
|
||||
import { Icon } from './controls/icons';
|
||||
import { StructureComponentControls } from './structure/components';
|
||||
import { StructureSourceControls } from './structure/source';
|
||||
import { VolumeStreamingControls } from './structure/volume';
|
||||
import { StructureMeasurementsControls } from './structure/measurements';
|
||||
|
||||
export class TrajectoryViewportControls extends PluginUIComponent<{}, { show: boolean, label: string }> {
|
||||
state = { show: false, label: '' }
|
||||
@@ -116,7 +116,7 @@ export class StateSnapshotViewportControls extends PluginUIComponent<{}, { isBus
|
||||
componentDidMount() {
|
||||
// TODO: this needs to be diabled when the state is updating!
|
||||
this.subscribe(this.plugin.state.snapshots.events.changed, () => this.forceUpdate());
|
||||
this.subscribe(this.plugin.behaviors.state.isUpdating, isBusy => this.setState({ isBusy }));
|
||||
this.subscribe(this.plugin.behaviors.state.isBusy, isBusy => this.setState({ isBusy }));
|
||||
this.subscribe(this.plugin.behaviors.state.isAnimating, isBusy => this.setState({ isBusy }))
|
||||
|
||||
window.addEventListener('keyup', this.keyUp, false);
|
||||
@@ -202,17 +202,17 @@ export class StateSnapshotViewportControls extends PluginUIComponent<{}, { isBus
|
||||
}
|
||||
}
|
||||
|
||||
export class AnimationViewportControls extends PluginUIComponent<{}, { isEmpty: boolean, isExpanded: boolean, isUpdating: boolean, isAnimating: boolean, isPlaying: boolean }> {
|
||||
state = { isEmpty: true, isExpanded: false, isUpdating: false, isAnimating: false, isPlaying: false };
|
||||
export class AnimationViewportControls extends PluginUIComponent<{}, { isEmpty: boolean, isExpanded: boolean, isBusy: boolean, isAnimating: boolean, isPlaying: boolean }> {
|
||||
state = { isEmpty: true, isExpanded: false, isBusy: false, isAnimating: false, isPlaying: false };
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.state.snapshots.events.changed, () => {
|
||||
if (this.plugin.state.snapshots.state.isPlaying) this.setState({ isPlaying: true, isExpanded: false });
|
||||
else this.setState({ isPlaying: false });
|
||||
});
|
||||
this.subscribe(this.plugin.behaviors.state.isUpdating, isUpdating => {
|
||||
if (isUpdating) this.setState({ isUpdating: true, isExpanded: false, isEmpty: this.plugin.state.data.tree.transforms.size < 2 });
|
||||
else this.setState({ isUpdating: false, isEmpty: this.plugin.state.data.tree.transforms.size < 2 });
|
||||
this.subscribe(this.plugin.behaviors.state.isBusy, isBusy => {
|
||||
if (isBusy) this.setState({ isBusy: true, isExpanded: false, isEmpty: this.plugin.state.data.tree.transforms.size < 2 });
|
||||
else this.setState({ isBusy: false, isEmpty: this.plugin.state.data.tree.transforms.size < 2 });
|
||||
});
|
||||
this.subscribe(this.plugin.behaviors.state.isAnimating, isAnimating => {
|
||||
if (isAnimating) this.setState({ isAnimating: true, isExpanded: false });
|
||||
@@ -235,11 +235,11 @@ export class AnimationViewportControls extends PluginUIComponent<{}, { isEmpty:
|
||||
return <div className='msp-animation-viewport-controls'>
|
||||
<div>
|
||||
<div className='msp-semi-transparent-background' />
|
||||
<IconButton icon={isAnimating || isPlaying ? 'stop' : 'tape'} title={isAnimating ? 'Stop' : 'Select Animation'}
|
||||
<IconButton icon={isAnimating || isPlaying ? 'stop' : 'tape'} transparent title={isAnimating ? 'Stop' : 'Select Animation'}
|
||||
onClick={isAnimating || isPlaying ? this.stop : this.toggleExpanded}
|
||||
disabled={isAnimating|| isPlaying ? false : this.state.isUpdating || this.state.isPlaying || this.state.isEmpty} />
|
||||
disabled={isAnimating|| isPlaying ? false : this.state.isBusy || this.state.isPlaying || this.state.isEmpty} />
|
||||
</div>
|
||||
{(this.state.isExpanded && !this.state.isUpdating) && <div className='msp-animation-viewport-controls-select'>
|
||||
{(this.state.isExpanded && !this.state.isBusy) && <div className='msp-animation-viewport-controls-select'>
|
||||
<AnimationControls onStart={this.toggleExpanded} />
|
||||
</div>}
|
||||
</div>;
|
||||
@@ -264,16 +264,32 @@ export class LociLabels extends PluginUIComponent<{}, { entries: ReadonlyArray<L
|
||||
}
|
||||
}
|
||||
|
||||
export class CustomStructureControls extends PluginUIComponent<{ initiallyCollapsed?: boolean }> {
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.state.behaviors.events.changed, () => this.forceUpdate());
|
||||
}
|
||||
|
||||
render() {
|
||||
const controls: JSX.Element[] = []
|
||||
this.plugin.customStructureControls.forEach((Controls, key) => {
|
||||
controls.push(<Controls initiallyCollapsed={this.props.initiallyCollapsed} key={key} />)
|
||||
})
|
||||
return controls.length > 0 ? <>{controls}</> : null
|
||||
}
|
||||
}
|
||||
|
||||
export class DefaultStructureTools extends PluginUIComponent {
|
||||
render() {
|
||||
return <>
|
||||
<div className='msp-section-header'><Icon name='tools' /> Structure Tools</div>
|
||||
<div className='msp-section-header'><Icon name='tools' />Structure Tools</div>
|
||||
|
||||
<StructureSourceControls />
|
||||
<StructureSelectionControls />
|
||||
<StructureComponentControls />
|
||||
<StructureMeasurementsControls />
|
||||
<StructureComponentControls />
|
||||
<VolumeStreamingControls />
|
||||
|
||||
<CustomStructureControls />
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user