Compare commits

...

105 Commits

Author SHA1 Message Date
Alexander Rose
472def49f6 0.6.0-dev.9 2020-03-31 10:17:54 -07:00
Alexander Rose
90549893e3 added pdbx_entity_instance_feature to mmcif schema 2020-03-31 09:49:18 -07:00
Alexander Rose
73a8e45202 added missing pdbx_branch_scheme fields to mmcif schema 2020-03-31 09:26:29 -07:00
David Sehnal
35df55cb4f CSS & structure type auto-apply tweaks 2020-03-31 17:19:03 +02:00
David Sehnal
c2028d20a8 mol-plugin: do not disable UI for fast updates 2020-03-31 16:35:45 +02:00
David Sehnal
eaffdc6a98 mol-plugin-ui: do not auto-apply symmetry structure types 2020-03-31 15:52:56 +02:00
David Sehnal
0b1e6100a9 mol-canvas-3d: fix camera transition when adding new representations 2020-03-31 15:25:00 +02:00
David Sehnal
2168905c11 StructureHierarchyManager.updateCurrent fix 2020-03-31 14:57:49 +02:00
David Sehnal
f955e6a299 mol-plugin-state: changing structure "type" recreates the subtree
* different assemblies can have different default components
* i.e. assembly 1 doesn't have carbs and asm 2 does
2020-03-31 14:55:59 +02:00
David Sehnal
98f3981e12 Fix CSS issues 2020-03-31 14:12:45 +02:00
Alexander Rose
82f94d20ea wip, cellpack file input 2020-03-31 02:04:18 -07:00
Alexander Rose
fbc6d47117 wip, cellpack loader update 2020-03-31 01:19:07 -07:00
Alexander Rose
6a2e4cf813 handle orphan models and structures in StructureHierarchyManager 2020-03-31 01:18:41 -07:00
Alexander Rose
4aabef7683 package updates 2020-03-31 00:48:40 -07:00
Alexander Rose
74ae91ee1b support initiallyCollapsed in CustomStructureControls 2020-03-31 00:24:17 -07:00
Alexander Rose
8b62766474 add data-color to ButtonProps (for color swatches) 2020-03-30 23:03:20 -07:00
Alexander Rose
2995504916 made StructureMeasurementsControls seperate panel again 2020-03-30 22:56:01 -07:00
Alexander Rose
63f6848d26 0.6.0-dev.8 2020-03-30 22:35:58 -07:00
Alexander Rose
988e429693 Merge branch 'master' of https://github.com/molstar/molstar 2020-03-30 19:27:35 -07:00
David Sehnal
ba1c6ef046 CameraTransitionManager.apply reverse change 2020-03-31 03:49:20 +02:00
David Sehnal
7ceff92a4e fix structure focus camera animation
+ more UI code tweaks
2020-03-31 03:45:48 +02:00
Alexander Rose
1f968b2836 Merge branch 'master' of https://github.com/molstar/molstar 2020-03-30 18:37:14 -07:00
Alexander Rose
d97d7e3b14 AssemblySymmetryControls fixes 2020-03-30 18:37:03 -07:00
Alexander Rose
5c4c4811e4 added .props() to CustomProperty.Provider 2020-03-30 18:35:30 -07:00
David Sehnal
f2a6e63a20 mol-plugin-ui: general refactoring & code improvements 2020-03-31 02:35:58 +02:00
David Sehnal
1aace4a26f mol-plugin-ui: added Button common ctrl 2020-03-31 01:43:04 +02:00
David Sehnal
b1c140d23e IconButton flex prop 2020-03-31 00:42:54 +02:00
David Sehnal
ee16212c31 Merge branch 'master' of https://github.com/molstar/molstar 2020-03-31 00:14:29 +02:00
David Sehnal
f6d232b1c5 mol-plugin-ui: removed unused CSS 2020-03-31 00:14:13 +02:00
Alexander Rose
c8a002933e take PluginContext.spec into account for SimpleSettingsMapping.layout 2020-03-30 14:55:28 -07:00
Alexander Rose
4757ca9913 fix clashes variable in validationReportPreset 2020-03-30 14:30:00 -07:00
Alexander Rose
5264c75e37 Merge branch 'master' of https://github.com/molstar/molstar 2020-03-30 12:29:23 -07:00
Alexander Rose
032bf44863 package updates (npm audit fix) 2020-03-30 12:02:28 -07:00
David Sehnal
2f4f5e43f3 StructureFocusManager.behaviors.current
replaces events.changed
2020-03-30 20:57:39 +02:00
Alexander Rose
7b1edcadf6 fous fixes and improvements 2020-03-30 11:53:55 -07:00
Alexander Rose
f0f74d182d updated schemas 2020-03-30 11:36:37 -07:00
Alexander Rose
59142adbbc updated packages 2020-03-30 11:26:09 -07:00
Alexander Rose
2fda8c5db1 tweaked assembly symmetry ui 2020-03-30 11:21:07 -07:00
Alexander Rose
7b5efa3e42 LociLabelManager: fold and count identical labels 2020-03-30 10:05:14 -07:00
Alexander Rose
d784d202bd volume streaming: ensure current focus is shown upon registering 2020-03-30 09:52:48 -07:00
Alexander Rose
8833474a43 structure size and quality threshold improvements 2020-03-30 09:51:04 -07:00
David Sehnal
57cbb2f8b6 StructureSourceControls: Fix crash when selecting "Symmetry (assembly)" 2020-03-28 18:01:56 +01:00
David Sehnal
b6112a914f StateTransformer.dispose
- use in StructureRepresentation3D to release custom props
2020-03-28 17:45:16 +01:00
David Sehnal
2008f8538c Various tweaks and fixes
- allow to hide expand button in Viewport
- principal axes fix for single element
- fixes to preset syncing
- CSS fixes
- picking level grouping
2020-03-28 16:46:47 +01:00
David Sehnal
09fba43a1c mol-plugin-state: representation presets "sync" instead of recreate 2020-03-28 14:00:01 +01:00
David Sehnal
b76c3613f9 Focus/Selection UI tweaks 2020-03-28 12:38:39 +01:00
Alexander Rose
cf4ddcb587 label tweaks 2020-03-27 23:42:03 -07:00
Alexander Rose
696106f48b check distance for IntraUnitClashes 2020-03-27 21:54:30 -07:00
Alexander Rose
fb286cd9cf take pixelRatio into account for camera-helper 2020-03-27 21:44:02 -07:00
Alexander Rose
5121bd700e changed assembly-symmetry preset to StructureRepresentationPresetProvider 2020-03-27 21:25:18 -07:00
Alexander Rose
6173520ad0 add validation-report preset 2020-03-27 20:09:17 -07:00
Alexander Rose
a7189232dd wip, StructureFocus controls and manager 2020-03-27 20:00:02 -07:00
Alexander Rose
4a96b45b04 added StructureSelectionQueryRegistry
- register hasClashes, isAccessible, isBuried from their custom props
2020-03-27 19:51:19 -07:00
David Sehnal
20ac549dd6 Plugin UI tweaks
- component focus ensures visibility
- moved measurements to selection section
2020-03-27 18:46:27 +01:00
David Sehnal
38be00c0b7 fix StateTreeNode display bug 2020-03-27 17:10:44 +01:00
David Sehnal
0a9bdc8cf6 model-server: query-many (wip)
+ some UI changes
2020-03-27 16:01:29 +01:00
David Sehnal
0b4318280a cosmetic UI changes 2020-03-27 12:29:51 +01:00
Alexander Rose
b1da60e1c0 wip, focus manager, focus repr refactoring 2020-03-26 18:32:21 -07:00
David Sehnal
2cc5987f9e cosmetic UI changes 2020-03-26 15:52:23 +01:00
David Sehnal
745415a1d8 rename first-model trajectory preset to default 2020-03-26 14:09:50 +01:00
David Sehnal
c80658f368 hide reprLabel in StructureComponentGroup 2020-03-26 13:15:27 +01:00
David Sehnal
033675a417 mol-plugin-ui: GenericEntry.showOnFocus option 2020-03-26 13:09:09 +01:00
David Sehnal
3dd57e9dc8 PD.ColorList refactoring 2020-03-26 12:49:31 +01:00
Alexander Rose
1e3daa6c98 fixed color-theme param getters for palette.scale.list.predefined 2020-03-25 19:44:57 -07:00
Alexander Rose
8855f51cfc Merge branch 'master' of https://github.com/molstar/molstar 2020-03-25 19:33:13 -07:00
Alexander Rose
50d8debb2b refactored assembly-symmetry
- provide a single symmetry-index, story data in AssemblySymmetryDataProvider
2020-03-25 19:32:30 -07:00
Alexander Rose
e682eb78b0 various ui tweaks 2020-03-25 19:31:39 -07:00
Alexander Rose
bd2bbf3e2d plugin: added customStructureControls renamed genericRepresentationControls 2020-03-25 19:30:46 -07:00
Alexander Rose
f38f040aea updated DefaultCellPackBaseUrl 2020-03-25 19:25:02 -07:00
David Sehnal
f912b2d802 proteopedia wrapper bugfix 2020-03-25 21:20:51 +01:00
David Sehnal
0ad03e6a0b custom interpolation list for color palette 2020-03-25 20:46:14 +01:00
Alexander Rose
160d8c529e Merge branch 'master' of https://github.com/molstar/molstar 2020-03-25 10:42:43 -07:00
David Sehnal
28edfd810c mol-model: molstar_atom_site_operator_mapping parsing 2020-03-25 18:29:10 +01:00
Alexander Rose
7b931cfb66 wip, assembly-symmetry preset 2020-03-25 10:28:55 -07:00
David Sehnal
5575c61577 mmcif export updates- rename chains with asm/symm/ncs operators- molstar_atom_site_operator_mapping category 2020-03-25 17:32:15 +01:00
Alexander Rose
8f93dce105 tweaked Structure.hasHighSymmetry 2020-03-25 08:48:26 -07:00
David Sehnal
0eb3d7226a SymmetryOperator.create refactor, added SymmetryOperator.suffix 2020-03-25 15:56:20 +01:00
Alexander Rose
a9533b666c added TrajectoryHierarchyBuilder 2020-03-24 19:23:13 -07:00
David Sehnal
6d67b4db56 update camera axes default params to make it less distracting 2020-03-24 11:46:38 +01:00
David Sehnal
1b5eff6454 ui improvements
- state tree update/apply transform
- focus label, customize radius
- StructureComponentGroup repr label
2020-03-24 11:41:30 +01:00
Alexander Rose
83fb28cc9d wip, generic representation entry 2020-03-23 19:31:07 -07:00
Alexander Rose
c39ffc0b0e fix Infinity in projection matrix
- ensure near and far are not identical
2020-03-23 19:30:18 -07:00
Alexander Rose
23892cfbdd fixed genericTarget in hierarchy-state tagMap 2020-03-23 19:05:45 -07:00
Alexander Rose
3bdabc444d camera helper param tweaks 2020-03-23 15:14:16 -07:00
Alexander Rose
c233be4467 set isoValue param as essential 2020-03-23 15:08:54 -07:00
Alexander Rose
8468f33803 Merge branch 'master' of https://github.com/molstar/molstar 2020-03-23 14:56:55 -07:00
Alexander Rose
0e77369fdb added camera helper showing orientation axes 2020-03-23 14:55:54 -07:00
Alexander Rose
9fcc8e7977 param handling tweaks 2020-03-23 12:16:57 -07:00
David Sehnal
538371ced8 Remove StructureBuilderTags.Component
+ remove some unused code
2020-03-23 19:11:00 +01:00
Alexander Rose
380887bd22 improved trackball.focusCamera
- radius change is now relative to current radius and not fixed
2020-03-23 11:03:35 -07:00
Alexander Rose
17b4b1cb86 added visible bounding sphere to debug helper 2020-03-23 11:01:33 -07:00
Alexander Rose
3e7c358c07 improved unitcell bounding sphere (added extrema) 2020-03-23 11:00:51 -07:00
Alexander Rose
05b592a173 removed unused runTask canvas3d argument 2020-03-23 11:00:12 -07:00
Alexander Rose
e1d0515fae unified font and svg icons 2020-03-23 10:48:02 -07:00
David Sehnal
d4d3b9645e treat "current interaction" as a "structure component" 2020-03-23 18:14:01 +01:00
David Sehnal
d12d99dcfa remove unused StructureBuilderTags 2020-03-23 17:57:28 +01:00
David Sehnal
174324d21c added 'reds-darker' color list 2020-03-23 15:24:49 +01:00
David Sehnal
26156a5982 ProteopediaWrapper: snapshots 2020-03-23 15:18:40 +01:00
David Sehnal
af5ddf6950 StructureHierarchy now relies less on tags 2020-03-23 15:03:38 +01:00
David Sehnal
2ef35e5fb9 CustomElementProperty label fix 2020-03-23 13:43:52 +01:00
David Sehnal
513bfeaae7 StructureHierarchyManager sync on demand
+ PluginComponent updates
2020-03-23 11:01:37 +01:00
David Sehnal
7311e6f484 mol-plugin-state: StructureCoordinateSystem decorator (wip)
+ SymmetryOperator.assembly now optional
2020-03-22 22:08:37 +01:00
David Sehnal
90ecedcae8 mol-state: StateTransformer.Definition.isDecorator
- moved from StateTransform.isDecorator
- when using StateBuilder.apply:
  * decorators are always "inserted"
  * if a node has a decorator, the transformer is applied to the decorator instead (recursive)
2020-03-22 15:09:33 +01:00
David Sehnal
352a20ac48 camera manager focus multiple loci/spheres
+ show Unitcell control for multiple structures
+ misc fixes
2020-03-21 13:07:35 +01:00
David Sehnal
b1d8c5f6ea mol-plugin-ui: left panel tweak 2020-03-21 12:16:45 +01:00
285 changed files with 8292 additions and 15495 deletions

3457
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "0.6.0-dev.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",

View File

@@ -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) {

View File

@@ -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(', ');
}

View File

@@ -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
// })
// )
// }
}));

View 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 };
}
});

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -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' }) { }

View File

@@ -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);

View File

@@ -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();
});
////////////////////////////////////////////////////////

View File

@@ -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);
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -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;

View File

@@ -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) {

View File

@@ -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: {} },

View File

@@ -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)

View File

@@ -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()

View 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]
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -11,6 +11,7 @@ import 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)
}
}
}

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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)
}
}

View File

@@ -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);

View File

@@ -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.
*/

View File

@@ -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

View File

@@ -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
*/

View File

@@ -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.

View File

@@ -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++) {

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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()

View File

@@ -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[]) {

View File

@@ -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 }

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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)

View File

@@ -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 };
}

View File

@@ -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,

View File

@@ -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;

View File

@@ -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)

View File

@@ -69,7 +69,8 @@ namespace CustomModelProperty {
// this invalidates property.value
set(data, p, undefined)
}
}
},
props: (data: Model) => get(data).props,
}
}
}

View File

@@ -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> {

View File

@@ -72,7 +72,8 @@ namespace CustomStructureProperty {
// this invalidates property.value
set(data, p, value)
}
}
},
props: (data: Structure) => get(data).props,
}
}
}

View File

@@ -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)
}
})

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
})

View File

@@ -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)
}
}

View File

@@ -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

View File

@@ -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>`,

View File

@@ -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)
}
})

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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) {

View File

@@ -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'

View File

@@ -69,6 +69,6 @@ class CustomProperties {
}
has(desc: CustomPropertyDescriptor<any>): boolean {
return this._refs.has(desc);
return this._set.has(desc);
}
}

View File

@@ -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> = {

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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 */

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -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)

View File

@@ -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));
}
}

View File

@@ -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),
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -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);
}
}

View File

@@ -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,

View File

@@ -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;

View File

@@ -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

View File

@@ -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();

View File

@@ -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;

View 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));
}
}

View File

@@ -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;

View File

@@ -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
}
}

View File

@@ -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()
};

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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 };
}
}

View File

@@ -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;

View File

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

View File

@@ -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]))
}
}

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author 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('')
};

View 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));
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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)
};

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -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',

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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>;
}

View File

@@ -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