mirror of
https://github.com/molstar/molstar.git
synced 2026-06-06 06:34:23 +08:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
897d17c8ed | ||
|
|
ca866cfa3a | ||
|
|
115824bbcf | ||
|
|
438de5760d | ||
|
|
3f765aedec | ||
|
|
96144fb10f | ||
|
|
cc34425712 | ||
|
|
9d68838893 | ||
|
|
6c68cebca0 | ||
|
|
b3e784262d | ||
|
|
2fdc22de71 | ||
|
|
77f0b0033f | ||
|
|
69da5abb88 | ||
|
|
a827e9a449 | ||
|
|
e74a29ae6a | ||
|
|
9b3d2f396e | ||
|
|
14cf7cc101 | ||
|
|
e5293c4d36 | ||
|
|
3afe21a4c3 | ||
|
|
bb50b69bb4 | ||
|
|
af7c030338 | ||
|
|
34b1eee876 | ||
|
|
c9cd1075d3 | ||
|
|
4fa04f0ff8 | ||
|
|
75f6466fd7 | ||
|
|
503ffd80fd | ||
|
|
c8ac64a571 | ||
|
|
bf81b902bd | ||
|
|
b636cdf9cc | ||
|
|
2d3b85825a | ||
|
|
a5a34f39e0 | ||
|
|
fe7e04f61b | ||
|
|
1a14720e35 | ||
|
|
18bf743ed2 | ||
|
|
f8d085a034 | ||
|
|
94bf3a136c | ||
|
|
9e8a8f3e71 | ||
|
|
45f9d93f3a | ||
|
|
44a566fdf3 | ||
|
|
d7f7770b7c | ||
|
|
df6b163505 | ||
|
|
51f88fff71 | ||
|
|
6b8db5abc6 | ||
|
|
37a0b07d56 |
16
.vscode/extensions.json
vendored
Normal file
16
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||
|
||||
// List of extensions which should be recommended for users of this workspace.
|
||||
"recommendations": [
|
||||
"ms-vscode.vscode-typescript-tslint-plugin",
|
||||
"slevesque.shader",
|
||||
"stpn.vscode-graphql",
|
||||
"wayou.vscode-todo-highlight"
|
||||
],
|
||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||
"unwantedRecommendations": [
|
||||
|
||||
]
|
||||
}
|
||||
@@ -95,7 +95,7 @@ Install CIFTools `npm install ciftools -g`
|
||||
### Other scripts
|
||||
**Create chem comp bond table**
|
||||
|
||||
export NODE_PATH="lib"; node --max-old-space-size=8192 build/src/apps/chem-comp-bond/create-table.js build/data/ccb.bcif -b
|
||||
export NODE_PATH="lib"; node --max-old-space-size=4096 lib/apps/chem-comp-bond/create-table.js build/data/ccb.bcif -b
|
||||
|
||||
**Test model server**
|
||||
|
||||
@@ -133,14 +133,17 @@ To get syntax highlighting for shader and graphql files add the following to Vis
|
||||
|
||||
## Publish
|
||||
|
||||
## Prerelease
|
||||
### Prerelease
|
||||
npm version prerelease # asumes the current version ends with '-dev.X'
|
||||
npm publish --tag next
|
||||
|
||||
## Release
|
||||
### Release
|
||||
npm version 0.X.0 # provide valid semver string
|
||||
npm publish
|
||||
|
||||
## Deploy
|
||||
node ./scripts/deploy.js # currently updates the viewer on molstar.org/viewer
|
||||
|
||||
## Contributing
|
||||
Just open an issue or make a pull request. All contributions are welcome.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ const path = require('path')
|
||||
const basePath = path.join(__dirname, '..', '..', 'src', 'mol-model-props', 'rcsb', 'graphql')
|
||||
|
||||
generate({
|
||||
schema: 'http://rest-dev.rcsb.org/graphql',
|
||||
schema: 'http://rest-staging.rcsb.org/graphql',
|
||||
documents: {
|
||||
[path.join(basePath, 'symmetry.gql.ts')]: {
|
||||
loader: path.join(__dirname, 'loader.js')
|
||||
|
||||
1077
package-lock.json
generated
1077
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
25
package.json
25
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.3",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -65,9 +65,10 @@
|
||||
"circular-dependency-plugin": "^5.0.2",
|
||||
"concurrently": "^4.1.0",
|
||||
"cpx": "^1.5.0",
|
||||
"css-loader": "^2.1.1",
|
||||
"css-loader": "^3.0.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^3.0.1",
|
||||
"file-loader": "^4.0.0",
|
||||
"fs-extra": "^8.0.1",
|
||||
"graphql-code-generator": "^0.18.2",
|
||||
"graphql-codegen-time": "^0.18.2",
|
||||
"graphql-codegen-typescript-template": "^0.18.2",
|
||||
@@ -75,26 +76,25 @@
|
||||
"jest-raw-loader": "^1.0.1",
|
||||
"mini-css-extract-plugin": "^0.7.0",
|
||||
"node-sass": "^4.12.0",
|
||||
"raw-loader": "^2.0.0",
|
||||
"raw-loader": "^3.0.0",
|
||||
"resolve-url-loader": "^3.1.0",
|
||||
"sass-loader": "^7.1.0",
|
||||
"simple-git": "^1.113.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"ts-jest": "^24.0.2",
|
||||
"tslint": "^5.17.0",
|
||||
"typescript": "^3.5.1",
|
||||
"uglify-js": "^3.6.0",
|
||||
"util.promisify": "^1.0.0",
|
||||
"webpack": "^4.32.2",
|
||||
"webpack-cli": "^3.3.2"
|
||||
"webpack": "^4.33.0",
|
||||
"webpack-cli": "^3.3.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/argparse": "^1.0.36",
|
||||
"@types/benchmark": "^1.0.31",
|
||||
"@types/compression": "0.0.36",
|
||||
"@types/express": "^4.16.1",
|
||||
"@types/express": "^4.17.0",
|
||||
"@types/jest": "^24.0.13",
|
||||
"@types/node": "^12.0.4",
|
||||
"@types/node-fetch": "^2.3.4",
|
||||
"@types/node": "^12.0.8",
|
||||
"@types/node-fetch": "^2.3.5",
|
||||
"@types/react": "^16.8.19",
|
||||
"@types/react-dom": "^16.8.4",
|
||||
"@types/swagger-ui-dist": "3.0.0",
|
||||
@@ -108,7 +108,8 @@
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"rxjs": "^6.5.2",
|
||||
"swagger-ui-dist": "^3.22.2",
|
||||
"swagger-ui-dist": "^3.22.3",
|
||||
"util.promisify": "^1.0.0",
|
||||
"xhr2": "^0.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
59
scripts/deploy.js
Normal file
59
scripts/deploy.js
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
const git = require('simple-git')
|
||||
const path = require('path')
|
||||
const fs = require("fs")
|
||||
const fse = require("fs-extra")
|
||||
|
||||
const remoteUrl = "https://github.com/molstar/molstar.github.io.git"
|
||||
const buildDir = path.resolve(__dirname, '../build/')
|
||||
const deployDir = path.resolve(buildDir, 'deploy/')
|
||||
const localPath = path.resolve(deployDir, 'molstar.github.io/')
|
||||
|
||||
function log(command, stdout, stderr) {
|
||||
if (command) {
|
||||
console.log('\n###', command)
|
||||
stdout.pipe(process.stdout)
|
||||
stderr.pipe(process.stderr)
|
||||
}
|
||||
}
|
||||
|
||||
function copyViewer() {
|
||||
console.log('\n###', 'copy viewer files')
|
||||
const viewerBuildPath = path.resolve(buildDir, '../build/viewer/')
|
||||
const viewerDeployPath = path.resolve(localPath, 'viewer/')
|
||||
fse.copySync(viewerBuildPath, viewerDeployPath, { overwrite: true })
|
||||
}
|
||||
|
||||
if (!fs.existsSync(localPath)) {
|
||||
console.log('\n###', 'create localPath')
|
||||
fs.mkdirSync(localPath, { recursive: true })
|
||||
}
|
||||
|
||||
process.chdir(localPath);
|
||||
|
||||
if (!fs.existsSync(path.resolve(localPath, '.git/'))) {
|
||||
console.log('\n###', 'clone repository')
|
||||
git()
|
||||
.outputHandler(log)
|
||||
.clone(remoteUrl, localPath)
|
||||
.fetch(['--all'])
|
||||
.exec(copyViewer)
|
||||
.add(['-A'])
|
||||
.commit('updated viewer')
|
||||
.push()
|
||||
} else {
|
||||
console.log('\n###', 'update repository')
|
||||
git()
|
||||
.outputHandler(log)
|
||||
.fetch(['--all'])
|
||||
.reset(['--hard', 'origin/master'])
|
||||
.exec(copyViewer)
|
||||
.add(['-A'])
|
||||
.commit('updated viewer')
|
||||
.push()
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -15,12 +15,13 @@ const readFile = util.promisify(fs.readFile)
|
||||
const writeFile = util.promisify(fs.writeFile)
|
||||
|
||||
import { Progress } from '../../mol-task'
|
||||
import { Database, Table, DatabaseCollection, Column } from '../../mol-data/db'
|
||||
import { Database, Table, DatabaseCollection } from '../../mol-data/db'
|
||||
import { CIF } from '../../mol-io/reader/cif'
|
||||
import { CifWriter } from '../../mol-io/writer/cif'
|
||||
import { CCD_Schema } from '../../mol-io/reader/cif/schema/ccd'
|
||||
import { SetUtils } from '../../mol-util/set'
|
||||
import { DefaultMap } from '../../mol-util/map'
|
||||
import { mmCIF_chemCompBond_schema } from '../../mol-io/reader/cif/schema/mmcif-extras';
|
||||
|
||||
export async function ensureAvailable(path: string, url: string) {
|
||||
if (FORCE_DOWNLOAD || !fs.existsSync(path)) {
|
||||
@@ -74,16 +75,6 @@ export function getEncodedCif(name: string, database: Database<Database.Schema>,
|
||||
type CCB = Table<CCD_Schema['chem_comp_bond']>
|
||||
type CCA = Table<CCD_Schema['chem_comp_atom']>
|
||||
|
||||
const ChemCompBond_Schema = {
|
||||
comp_id: CCD_Schema['chem_comp_bond'].comp_id,
|
||||
atom_id_1: CCD_Schema['chem_comp_bond'].atom_id_1,
|
||||
atom_id_2: CCD_Schema['chem_comp_bond'].atom_id_2,
|
||||
value_order: CCD_Schema['chem_comp_bond'].value_order,
|
||||
pdbx_aromatic_flag: CCD_Schema['chem_comp_bond'].pdbx_aromatic_flag,
|
||||
pdbx_stereo_config: CCD_Schema['chem_comp_bond'].pdbx_stereo_config,
|
||||
molstar_protonation_variant: Column.Schema.Str()
|
||||
}
|
||||
|
||||
function ccbKey(compId: string, atomId1: string, atomId2: string) {
|
||||
return atomId1 < atomId2 ? `${compId}:${atomId1}-${atomId2}` : `${compId}:${atomId2}-${atomId1}`
|
||||
}
|
||||
@@ -202,14 +193,14 @@ async function createBonds() {
|
||||
}
|
||||
}
|
||||
|
||||
const bondTable = Table.ofArrays(ChemCompBond_Schema, {
|
||||
const bondTable = Table.ofArrays(mmCIF_chemCompBond_schema, {
|
||||
comp_id, atom_id_1, atom_id_2, value_order,
|
||||
pdbx_aromatic_flag, pdbx_stereo_config, molstar_protonation_variant
|
||||
})
|
||||
|
||||
const bondDatabase = Database.ofTables(
|
||||
TABLE_NAME,
|
||||
{ chem_comp_bond: ChemCompBond_Schema },
|
||||
{ chem_comp_bond: mmCIF_chemCompBond_schema },
|
||||
{ chem_comp_bond: bondTable }
|
||||
)
|
||||
|
||||
@@ -220,12 +211,15 @@ async function run(out: string, binary = false) {
|
||||
const bonds = await createBonds()
|
||||
|
||||
const cif = getEncodedCif(TABLE_NAME, bonds, binary)
|
||||
if (!fs.existsSync(path.dirname(out))) {
|
||||
fs.mkdirSync(path.dirname(out));
|
||||
}
|
||||
writeFile(out, cif)
|
||||
}
|
||||
|
||||
const TABLE_NAME = 'CHEM_COMP_BONDS'
|
||||
|
||||
const DATA_DIR = path.join(__dirname, '..', '..', '..', 'data')
|
||||
const DATA_DIR = path.join(__dirname, '..', '..', '..', 'build/data')
|
||||
const CCD_PATH = path.join(DATA_DIR, 'components.cif')
|
||||
const PVCD_PATH = path.join(DATA_DIR, 'aa-variants-v1.cif')
|
||||
const CCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/components.cif'
|
||||
|
||||
@@ -29,6 +29,9 @@ function init() {
|
||||
initial: {
|
||||
isExpanded: true,
|
||||
showControls: !hideControls
|
||||
},
|
||||
controls: {
|
||||
...DefaultPluginSpec.layout && DefaultPluginSpec.layout.controls
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -105,6 +105,8 @@ export enum StateElements {
|
||||
ModelProps = 'model-props',
|
||||
Assembly = 'assembly',
|
||||
|
||||
VolumeStreaming = 'volume-streaming',
|
||||
|
||||
Sequence = 'sequence',
|
||||
SequenceVisual = 'sequence-visual',
|
||||
Het = 'het',
|
||||
|
||||
@@ -40,6 +40,13 @@
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#volume-streaming-wrapper {
|
||||
position: absolute;
|
||||
top: 100px;
|
||||
left: 780px;
|
||||
width: 300px;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="app.css" />
|
||||
<script type="text/javascript" src="./index.js"></script>
|
||||
@@ -55,6 +62,7 @@
|
||||
</select>
|
||||
</div>
|
||||
<div id="app"></div>
|
||||
<div id="volume-streaming-wrapper"></div>
|
||||
<script>
|
||||
// it might be a good idea to define these colors in a separate script file
|
||||
var CustomColors = [0x00ff00, 0x0000ff];
|
||||
@@ -66,7 +74,7 @@
|
||||
|
||||
function $(id) { return document.getElementById(id); }
|
||||
|
||||
var pdbId = '1eve', assemblyId= 'preferred';
|
||||
var pdbId = '1cbs', assemblyId= 'preferred';
|
||||
var url = 'https://www.ebi.ac.uk/pdbe/static/entry/' + pdbId + '_updated.cif';
|
||||
var format = 'cif';
|
||||
|
||||
@@ -151,6 +159,11 @@
|
||||
addHetGroupsContainer();
|
||||
|
||||
addSeparator();
|
||||
addHeader('Exp. Data');
|
||||
addControl('Init', () => PluginWrapper.experimentalData.init($('volume-streaming-wrapper')));
|
||||
addControl('Remove', () => PluginWrapper.experimentalData.remove());
|
||||
|
||||
addSeparator();
|
||||
addHeader('State');
|
||||
|
||||
var snapshot;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import './index.html'
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
@@ -13,11 +14,11 @@ import { StructureRepresentation3DHelpers } from '../../mol-plugin/state/transfo
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { PluginStateObject as PSO, PluginStateObject } from '../../mol-plugin/state/objects';
|
||||
import { AnimateModelIndex } from '../../mol-plugin/state/animation/built-in';
|
||||
import { StateBuilder, StateObject } from '../../mol-state';
|
||||
import { StateBuilder, StateObject, StateSelection } from '../../mol-state';
|
||||
import { EvolutionaryConservation } from './annotation';
|
||||
import { LoadParams, SupportedFormats, RepresentationStyle, ModelInfo, StateElements } from './helpers';
|
||||
import { RxEventHelper } from '../../mol-util/rx-event-helper';
|
||||
import { ControlsWrapper } from './ui/controls';
|
||||
import { ControlsWrapper, volumeStreamingControls } from './ui/controls';
|
||||
import { PluginState } from '../../mol-plugin/state';
|
||||
import { Scheduler } from '../../mol-task';
|
||||
import { createProteopediaCustomTheme } from './coloring';
|
||||
@@ -26,6 +27,8 @@ import { BuiltInStructureRepresentations } from '../../mol-repr/structure/regist
|
||||
import { BuiltInColorThemes } from '../../mol-theme/color';
|
||||
import { BuiltInSizeThemes } from '../../mol-theme/size';
|
||||
import { ColorNames } from '../../mol-util/color/tables';
|
||||
import { InitVolumeStreaming, CreateVolumeStreamingInfo } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
|
||||
import { ParamDefinition } from '../../mol-util/param-definition';
|
||||
// import { Vec3 } from 'mol-math/linear-algebra';
|
||||
// import { ParamDefinition } from 'mol-util/param-definition';
|
||||
// import { Text } from 'mol-geo/geometry/text/text';
|
||||
@@ -264,6 +267,28 @@ class MolStarProteopediaWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
private experimentalDataElement?: Element = void 0;
|
||||
experimentalData = {
|
||||
init: async (parent: Element) => {
|
||||
const asm = this.state.select(StateElements.Assembly)[0].obj!;
|
||||
const params = ParamDefinition.getDefaultValues(InitVolumeStreaming.definition.params!(asm, this.plugin));
|
||||
params.behaviorRef = StateElements.VolumeStreaming;
|
||||
params.defaultView = 'box';
|
||||
await this.plugin.runTask(this.state.applyAction(InitVolumeStreaming, params, StateElements.Assembly));
|
||||
this.experimentalDataElement = parent;
|
||||
volumeStreamingControls(this.plugin, parent);
|
||||
},
|
||||
remove: () => {
|
||||
const r = this.state.select(StateSelection.Generators.ofTransformer(CreateVolumeStreamingInfo))[0];
|
||||
if (!r) return;
|
||||
PluginCommands.State.RemoveObject.dispatch(this.plugin, { state: this.state, ref: r.transform.ref });
|
||||
if (this.experimentalDataElement) {
|
||||
ReactDOM.unmountComponentAtNode(this.experimentalDataElement);
|
||||
this.experimentalDataElement = void 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hetGroups = {
|
||||
reset: () => {
|
||||
const update = this.state.build().delete(StateElements.HetGroupFocus);
|
||||
|
||||
@@ -5,10 +5,14 @@
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { PluginUIComponent } from '../../../mol-plugin/ui/base';
|
||||
import { CurrentObject } from '../../../mol-plugin/ui/plugin';
|
||||
import { CurrentObject, PluginContextContainer } from '../../../mol-plugin/ui/plugin';
|
||||
import { AnimationControls } from '../../../mol-plugin/ui/state/animation';
|
||||
import { CameraSnapshots } from '../../../mol-plugin/ui/camera';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { TransformUpdaterControl } from '../../../mol-plugin/ui/state/update-transform';
|
||||
import { StateElements } from '../helpers';
|
||||
|
||||
export class ControlsWrapper extends PluginUIComponent {
|
||||
render() {
|
||||
@@ -18,4 +22,11 @@ export class ControlsWrapper extends PluginUIComponent {
|
||||
<CameraSnapshots />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export function volumeStreamingControls(plugin: PluginContext, parent: Element) {
|
||||
ReactDOM.render(<PluginContextContainer plugin={plugin}>
|
||||
<TransformUpdaterControl nodeRef={StateElements.VolumeStreaming} />
|
||||
</PluginContextContainer>,
|
||||
parent);
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
|
||||
export interface BooleanParamComponentProps {
|
||||
label: string
|
||||
param: PD.Boolean
|
||||
param: PD.BooleanParam
|
||||
value: boolean
|
||||
onChange(v: boolean): void
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ interface Canvas3D {
|
||||
const requestAnimationFrame = typeof window !== 'undefined' ? window.requestAnimationFrame : (f: (time: number) => void) => setImmediate(()=>f(Date.now()))
|
||||
|
||||
namespace Canvas3D {
|
||||
export interface HighlightEvent { current: Representation.Loci, prev: Representation.Loci, modifiers?: ModifiersKeys }
|
||||
export interface HighlightEvent { current: Representation.Loci, modifiers?: ModifiersKeys }
|
||||
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, modifiers: ModifiersKeys }
|
||||
|
||||
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}) {
|
||||
|
||||
@@ -59,7 +59,7 @@ export class Canvas3dInteractionHelper {
|
||||
|
||||
const loci = this.getLoci(this.id);
|
||||
if (!Representation.Loci.areEqual(this.prevLoci, loci)) {
|
||||
this.events.highlight.next({ current: loci, prev: this.prevLoci, modifiers: this.modifiers });
|
||||
this.events.highlight.next({ current: loci, modifiers: this.modifiers });
|
||||
this.prevLoci = loci;
|
||||
}
|
||||
}
|
||||
@@ -75,9 +75,8 @@ export class Canvas3dInteractionHelper {
|
||||
leave() {
|
||||
this.inside = false;
|
||||
if (this.prevLoci.loci !== EmptyLoci) {
|
||||
const prev = this.prevLoci;
|
||||
this.prevLoci = Representation.Loci.Empty;
|
||||
this.events.highlight.next({ current: this.prevLoci, prev });
|
||||
this.events.highlight.next({ current: this.prevLoci });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +98,7 @@ export class Canvas3dInteractionHelper {
|
||||
modify(modifiers: ModifiersKeys) {
|
||||
if (this.prevLoci.loci === EmptyLoci || ModifiersKeys.areEqual(modifiers, this.modifiers)) return;
|
||||
this.modifiers = modifiers;
|
||||
this.events.highlight.next({ current: this.prevLoci, prev: this.prevLoci, modifiers: this.modifiers });
|
||||
this.events.highlight.next({ current: this.prevLoci, modifiers: this.modifiers });
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -17,6 +17,7 @@ export const min = Tuple.fst;
|
||||
export function max(i: Tuple) { return Tuple.snd(i) - 1; }
|
||||
export function size(i: Tuple) { return Tuple.snd(i) - Tuple.fst(i); }
|
||||
export const hashCode = Tuple.hashCode;
|
||||
export const toString = Tuple.toString;
|
||||
|
||||
export function has(int: Tuple, v: number) { return Tuple.fst(int) <= v && v < Tuple.snd(int); }
|
||||
/** Returns the index of `x` in `set` or -1 if not found. */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -36,6 +36,8 @@ export function end(set: OrderedSetImpl) { return I.is(set) ? I.end(set) : S.end
|
||||
export function hashCode(set: OrderedSetImpl) { return I.is(set) ? I.hashCode(set) : S.hashCode(set); }
|
||||
// TODO: possibly add more hash functions to allow for multilevel hashing.
|
||||
|
||||
export function toString(set: OrderedSetImpl) { return I.is(set) ? I.toString(set) : S.toString(set); }
|
||||
|
||||
export function areEqual(a: OrderedSetImpl, b: OrderedSetImpl) {
|
||||
if (I.is(a)) {
|
||||
if (I.is(b)) return I.areEqual(a, b);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -35,6 +35,11 @@ export function hashCode(xs: Nums) {
|
||||
if (s > 2) return hash4(s, xs[0], xs[s - 1], xs[s >> 1]);
|
||||
return hash3(s, xs[0], xs[s - 1]);
|
||||
}
|
||||
export function toString(xs: Nums) {
|
||||
const s = xs.length;
|
||||
if (s > 5) return `[${xs[0]}, ${xs[1]}, ..., ${xs[s - 1]}], length ${s}`;
|
||||
return `[${(xs as number[]).join(', ')}]`;
|
||||
}
|
||||
|
||||
/** Returns the index of `x` in `set` or -1 if not found. */
|
||||
export function indexOf(xs: Nums, v: number) {
|
||||
|
||||
@@ -34,6 +34,8 @@ namespace Interval {
|
||||
export const size: <T extends number = number>(interval: Interval<T>) => number = Impl.size as any;
|
||||
/** Hash code describing the interval */
|
||||
export const hashCode: <T extends number = number>(interval: Interval<T>) => number = Impl.hashCode as any;
|
||||
/** String representation of the interval */
|
||||
export const toString: <T extends number = number>(interval: Interval<T>) => string = Impl.toString as any;
|
||||
|
||||
/** Test if two intervals are identical */
|
||||
export const areEqual: <T extends number = number>(a: Interval<T>, b: Interval<T>) => boolean = Impl.areEqual as any;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -8,7 +8,6 @@ import * as Base from './impl/ordered-set'
|
||||
import Interval from './interval'
|
||||
import SortedArray from './sorted-array';
|
||||
|
||||
/** test */
|
||||
namespace OrderedSet {
|
||||
export const Empty: OrderedSet = Base.Empty as any;
|
||||
export const ofSingleton: <T extends number = number>(value: T) => OrderedSet<T> = Base.ofSingleton as any;
|
||||
@@ -62,10 +61,12 @@ namespace OrderedSet {
|
||||
OrderedSet.forEach(set, v => array.push(v))
|
||||
return array
|
||||
}
|
||||
|
||||
export function toString<T extends number = number>(set: OrderedSet<T>): string {
|
||||
return Base.toString(set)
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents bla */
|
||||
type OrderedSet<T extends number = number> = SortedArray<T> | Interval<T>
|
||||
|
||||
|
||||
export default OrderedSet
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -31,6 +31,7 @@ namespace SortedArray {
|
||||
export const max: <T extends number = number>(array: SortedArray<T>) => T = Impl.max as any;
|
||||
export const size: <T extends number = number>(array: SortedArray<T>) => number = Impl.size as any;
|
||||
export const hashCode: <T extends number = number>(array: SortedArray<T>) => number = Impl.hashCode as any;
|
||||
export const toString: <T extends number = number>(array: SortedArray<T>) => string = Impl.toString as any;
|
||||
|
||||
export const areEqual: <T extends number = number>(a: SortedArray<T>, b: SortedArray<T>) => boolean = Impl.areEqual as any;
|
||||
export const areIntersecting: <T extends number = number>(a: SortedArray<T>, b: SortedArray<T>) => boolean = Impl.areIntersecting as any;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -73,6 +73,11 @@ namespace IntTuple {
|
||||
_float64[0] = t as any;
|
||||
return hash2(_int32[0], _int32[1]);
|
||||
}
|
||||
|
||||
export function toString(t: IntTuple) {
|
||||
_float64[0] = t as any;
|
||||
return `(${_int32[0]}, ${_int32[1]})`;
|
||||
}
|
||||
}
|
||||
|
||||
export default IntTuple
|
||||
@@ -24,7 +24,7 @@ export function applyOverpaintColor(array: Uint8Array, start: number, end: numbe
|
||||
}
|
||||
|
||||
export function clearOverpaint(array: Uint8Array, start: number, end: number) {
|
||||
array.fill(0, start, end)
|
||||
array.fill(0, start * 4, end * 4)
|
||||
}
|
||||
|
||||
export function createOverpaint(count: number, overpaintData?: OverpaintData): OverpaintData {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 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 { mmCIF_Schema } from './mmcif';
|
||||
import { mmCIF_Schema } from './mmcif';
|
||||
import { Column } from '../../../../mol-data/db';
|
||||
|
||||
export const mmCIF_residueId_schema = {
|
||||
label_comp_id: mmCIF_Schema.atom_site.label_comp_id,
|
||||
@@ -15,4 +17,10 @@ export const mmCIF_residueId_schema = {
|
||||
auth_comp_id: mmCIF_Schema.atom_site.auth_atom_id,
|
||||
auth_seq_id: mmCIF_Schema.atom_site.auth_seq_id,
|
||||
auth_asym_id: mmCIF_Schema.atom_site.auth_asym_id
|
||||
}
|
||||
|
||||
export const mmCIF_chemCompBond_schema = {
|
||||
...mmCIF_Schema.chem_comp_bond,
|
||||
/** Indicates if the bond entry was taken from the protonation variant dictionary */
|
||||
molstar_protonation_variant: Column.Schema.Str()
|
||||
}
|
||||
@@ -84,7 +84,7 @@ function handleMolecule(state: State) {
|
||||
molecule.mol_comment = getTokenString(tokenizer)
|
||||
}
|
||||
|
||||
function isStatus_bit(aString: String): Boolean {
|
||||
function isStatus_bit(aString: string): boolean {
|
||||
if (aString.includes('DSPMOD') || aString.includes('TYPECOL') || aString.includes('CAP')
|
||||
|| aString.includes('BACKBONE') || aString.includes('DICT') || aString.includes('ESSENTIAL')
|
||||
|| aString.includes('WATER') || aString.includes('DIRECT')) {
|
||||
|
||||
183
src/mol-io/writer/_spec/cif.spec.ts
Normal file
183
src/mol-io/writer/_spec/cif.spec.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import * as Data from '../../reader/cif/data-model'
|
||||
import { CifWriter } from '../cif';
|
||||
import decodeMsgPack from '../../common/msgpack/decode'
|
||||
import { EncodedFile, EncodedCategory } from '../../common/binary-cif';
|
||||
import Field from '../../reader/cif/binary/field';
|
||||
import * as C from '../cif/encoder';
|
||||
|
||||
const cartn_x = Data.CifField.ofNumbers([1.001, 1.002, 1.003, 1.004, 1.005, 1.006, 1.007, 1.008, 1.009]);
|
||||
const cartn_y = Data.CifField.ofNumbers([-3.0, -2.666, -2.3333, -2.0, -1.666, -1.333, -1.0, -0.666, -0.333]);
|
||||
const cartn_z = Data.CifField.ofNumbers([1, 2, 3, 4, 5, 6, 7, 8, 9].map(i => Math.sqrt(i)));
|
||||
const label_seq_id = Data.CifField.ofNumbers([1, 2, 3, 6, 11, 23, 47, 106, 235]);
|
||||
const atom_site = Data.CifCategory.ofFields('atom_site', { 'Cartn_x': cartn_x, 'Cartn_y': cartn_y, 'Cartn_z': cartn_z, 'label_seq_id': label_seq_id });
|
||||
const field1 = Data.CifField.ofNumbers([1, 2, 3, 6, 11, 23, 47, 106, 235]);
|
||||
const field2 = Data.CifField.ofNumbers([-1, -2, -3, -6, -11, -23, -47, -106, -235]);
|
||||
const other_fields = Data.CifCategory.ofFields('other_fields', { 'field1': field1, 'field2': field2 });
|
||||
|
||||
const encoding_aware_encoder = CifWriter.createEncoder({
|
||||
binary: true,
|
||||
binaryAutoClassifyEncoding: true,
|
||||
binaryEncodingPovider: CifWriter.createEncodingProviderFromJsonConfig([
|
||||
{
|
||||
'categoryName': 'atom_site',
|
||||
'columnName': 'Cartn_y',
|
||||
'encoding': 'rle',
|
||||
'precision': 0
|
||||
},
|
||||
{
|
||||
'categoryName': 'atom_site',
|
||||
'columnName': 'Cartn_z',
|
||||
'encoding': 'delta',
|
||||
'precision': 1
|
||||
},
|
||||
{
|
||||
'categoryName': 'atom_site',
|
||||
'columnName': 'label_seq_id',
|
||||
'encoding': 'delta-rle'
|
||||
}
|
||||
])
|
||||
});
|
||||
|
||||
describe('encoding-config', () => {
|
||||
const decoded = process(encoding_aware_encoder);
|
||||
|
||||
const decoded_atom_site = decoded.blocks[0].categories['atom_site'];
|
||||
const decoded_cartn_x = decoded_atom_site.getField('Cartn_x')!;
|
||||
const decoded_cartn_y = decoded_atom_site.getField('Cartn_y')!;
|
||||
const decoded_cartn_z = decoded_atom_site.getField('Cartn_z')!;
|
||||
const decoded_label_seq_id = decoded_atom_site.getField('label_seq_id')!;
|
||||
|
||||
const delta = 0.001;
|
||||
function assert(e: ArrayLike<number>, a: ArrayLike<number>) {
|
||||
expect(e.length).toBe(a.length);
|
||||
for (let i = 0; i < e.length; i++) {
|
||||
expect(Math.abs(e[i] - a[i])).toBeLessThan(delta);
|
||||
}
|
||||
}
|
||||
|
||||
function join(field: Data.CifField) {
|
||||
return field.binaryEncoding!.map(e => e.kind).join();
|
||||
}
|
||||
|
||||
it('strategy', () => {
|
||||
expect(join(decoded_cartn_x)).toBe('FixedPoint,Delta,IntegerPacking,ByteArray');
|
||||
expect(join(decoded_cartn_y)).toBe('FixedPoint,RunLength,IntegerPacking,ByteArray');
|
||||
expect(join(decoded_cartn_z)).toBe('FixedPoint,Delta,IntegerPacking,ByteArray');
|
||||
expect(join(decoded_label_seq_id)).toBe('Delta,RunLength,IntegerPacking,ByteArray');
|
||||
});
|
||||
|
||||
it('precision', () => {
|
||||
assert(decoded_cartn_x.toFloatArray(), cartn_x.toFloatArray());
|
||||
assert(decoded_cartn_y.toFloatArray(), cartn_y.toFloatArray().map(d => Math.round(d)));
|
||||
assert(decoded_cartn_z.toFloatArray(), cartn_z.toFloatArray().map(d => Math.round(d * 10) / 10));
|
||||
assert(decoded_label_seq_id.toIntArray(), label_seq_id.toIntArray());
|
||||
});
|
||||
});
|
||||
|
||||
const filter_aware_encoder1 = CifWriter.createEncoder({
|
||||
binary: true,
|
||||
binaryAutoClassifyEncoding: true
|
||||
});
|
||||
filter_aware_encoder1.setFilter(C.Category.filterOf('atom_site\n' +
|
||||
'\n' +
|
||||
'atom_site.Cartn_x\n' +
|
||||
'atom_site.Cartn_y\n'));
|
||||
|
||||
const filter_aware_encoder2 = CifWriter.createEncoder({
|
||||
binary: true
|
||||
});
|
||||
filter_aware_encoder2.setFilter(C.Category.filterOf('!atom_site\n' +
|
||||
'\n' +
|
||||
'!other_fields.field2\n'));
|
||||
|
||||
describe('filtering-config', () => {
|
||||
const decoded1 = process(filter_aware_encoder1);
|
||||
|
||||
const atom_site1 = decoded1.blocks[0].categories['atom_site'];
|
||||
const cartn_x1 = atom_site1.getField('Cartn_x');
|
||||
const cartn_y1 = atom_site1.getField('Cartn_y');
|
||||
const cartn_z1 = atom_site1.getField('Cartn_z');
|
||||
const label_seq_id1 = atom_site1.getField('label_seq_id');
|
||||
const fields1 = decoded1.blocks[0].categories['other_fields'];
|
||||
|
||||
it('whitelist-filtering', () => {
|
||||
expect(atom_site1).toBeDefined();
|
||||
expect(cartn_x1).toBeDefined();
|
||||
expect(cartn_y1).toBeDefined();
|
||||
expect(cartn_z1).toBeUndefined();
|
||||
expect(label_seq_id1).toBeUndefined();
|
||||
expect(fields1).toBeUndefined();
|
||||
});
|
||||
|
||||
const decoded2 = process(filter_aware_encoder2);
|
||||
|
||||
const atom_site2 = decoded2.blocks[0].categories['atom_site'];
|
||||
const fields2 = decoded2.blocks[0].categories['other_fields'];
|
||||
const field12 = fields2.getField('field1');
|
||||
const field22 = fields2.getField('field2');
|
||||
|
||||
it('blacklist-filtering', () => {
|
||||
expect(atom_site2).toBeUndefined();
|
||||
expect(fields2).toBeDefined();
|
||||
expect(field12).toBeDefined();
|
||||
expect(field22).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
function process(encoder: C.Encoder) {
|
||||
encoder.startDataBlock('test');
|
||||
|
||||
for (const cat of [atom_site, other_fields]) {
|
||||
const fields: CifWriter.Field[] = [];
|
||||
for (const f of cat.fieldNames) {
|
||||
fields.push(wrap(f, cat.getField(f)!))
|
||||
}
|
||||
encoder.writeCategory(getCategoryInstanceProvider(cat, fields));
|
||||
}
|
||||
|
||||
const encoded = encoder.getData() as Uint8Array;
|
||||
|
||||
const unpacked = decodeMsgPack(encoded) as EncodedFile;
|
||||
return Data.CifFile(unpacked.dataBlocks.map(block => {
|
||||
const cats = Object.create(null);
|
||||
for (const cat of block.categories) cats[cat.name.substr(1)] = Category(cat);
|
||||
return Data.CifBlock(block.categories.map(c => c.name.substr(1)), cats, block.header);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
function getCategoryInstanceProvider(cat: Data.CifCategory, fields: CifWriter.Field[]): CifWriter.Category {
|
||||
return {
|
||||
name: cat.name,
|
||||
instance: () => CifWriter.categoryInstance(fields, { data: cat, rowCount: cat.rowCount })
|
||||
};
|
||||
}
|
||||
|
||||
function wrap(name: string, field: Data.CifField): CifWriter.Field {
|
||||
const type = Data.getCifFieldType(field);
|
||||
if (type['@type'] === 'str') {
|
||||
return { name, type: CifWriter.Field.Type.Str, value: field.str, valueKind: field.valueKind };
|
||||
} else if (type['@type'] === 'float') {
|
||||
return { name, type: CifWriter.Field.Type.Float, value: field.float, valueKind: field.valueKind };
|
||||
} else {
|
||||
return { name, type: CifWriter.Field.Type.Int, value: field.int, valueKind: field.valueKind };
|
||||
}
|
||||
}
|
||||
|
||||
function Category(data: EncodedCategory): Data.CifCategory {
|
||||
const map = Object.create(null);
|
||||
const cache = Object.create(null);
|
||||
for (const col of data.columns) map[col.name] = col;
|
||||
return {
|
||||
rowCount: data.rowCount,
|
||||
name: data.name.substr(1),
|
||||
fieldNames: data.columns.map(c => c.name),
|
||||
getField(name) {
|
||||
const col = map[name];
|
||||
if (!col) return void 0;
|
||||
if (!!cache[name]) return cache[name];
|
||||
cache[name] = Field(col);
|
||||
return cache[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,5 +53,64 @@ export namespace CifWriter {
|
||||
return ff && ff.binaryEncoding ? ArrayEncoder.fromEncoding(ff.binaryEncoding) : void 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function createEncodingProviderFromJsonConfig(hints: EncodingStrategyHint[]): EncodingProvider {
|
||||
return {
|
||||
get(c, f) {
|
||||
for (let i = 0; i < hints.length; i++) {
|
||||
const hint = hints[i];
|
||||
if (hint.categoryName === c && hint.columnName === f) {
|
||||
return resolveEncoding(hint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resolveEncoding(hint: EncodingStrategyHint): ArrayEncoder {
|
||||
const precision: number | undefined = hint.precision;
|
||||
if (precision !== void 0) {
|
||||
const multiplier = Math.pow(10, precision);
|
||||
const fixedPoint = E.by(E.fixedPoint(multiplier));
|
||||
switch (hint.encoding) {
|
||||
case 'pack':
|
||||
return fixedPoint.and(E.integerPacking);
|
||||
case 'rle':
|
||||
return fixedPoint.and(E.runLength).and(E.integerPacking);
|
||||
case 'delta':
|
||||
return fixedPoint.and(E.delta).and(E.integerPacking);
|
||||
case 'delta-rle':
|
||||
return fixedPoint.and(E.delta).and(E.runLength).and(E.integerPacking);
|
||||
};
|
||||
} else {
|
||||
switch (hint.encoding) {
|
||||
case 'pack':
|
||||
return E.by(E.integerPacking);
|
||||
case 'rle':
|
||||
return E.by(E.runLength).and(E.integerPacking);
|
||||
case 'delta':
|
||||
return E.by(E.delta).and(E.integerPacking);
|
||||
case 'delta-rle':
|
||||
return E.by(E.delta).and(E.runLength).and(E.integerPacking);
|
||||
}
|
||||
}
|
||||
throw new Error('cannot be reached');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the information needed to encode certain fields: category and column name as well as encoding tag, precision is optional and identifies float columns.
|
||||
*/
|
||||
export interface EncodingStrategyHint {
|
||||
categoryName: string,
|
||||
columnName: string,
|
||||
// TODO would be nice to infer strategy and precision if needed
|
||||
encoding: EncodingType,
|
||||
/**
|
||||
* number of decimal places to keep - must be specified to float columns
|
||||
*/
|
||||
precision?: number
|
||||
}
|
||||
|
||||
type EncodingType = 'pack' | 'rle' | 'delta' | 'delta-rle'
|
||||
@@ -132,6 +132,60 @@ export namespace Category {
|
||||
includeField(categoryName: string, fieldName: string): boolean,
|
||||
}
|
||||
|
||||
export function filterOf(directives: string): Filter {
|
||||
const cat_whitelist: string[] = [];
|
||||
const cat_blacklist: string[] = [];
|
||||
const field_whitelist: string[] = [];
|
||||
const field_blacklist: string[] = [];
|
||||
|
||||
for (let d of directives.split(/[\r\n]+/)) {
|
||||
d = d.trim();
|
||||
// allow for empty lines in config
|
||||
if (d.length === 0) continue;
|
||||
// let ! denote blacklisted entries
|
||||
const blacklist = /^!/.test(d);
|
||||
if (blacklist) d = d.substr(1);
|
||||
const split = d.split(/\./);
|
||||
const field = split[1];
|
||||
const list = blacklist ? (field ? field_blacklist : cat_blacklist) : (field ? field_whitelist : cat_whitelist);
|
||||
|
||||
list[list.length] = d;
|
||||
|
||||
// ensure categories are aware about whitelisted columns
|
||||
if (field && !cat_whitelist.includes(split[0])) {
|
||||
cat_whitelist[cat_whitelist.length] = split[0];
|
||||
}
|
||||
}
|
||||
|
||||
const wlcatcol = field_whitelist.map(it => it.split('.')[0]);
|
||||
// blacklist has higher priority
|
||||
return {
|
||||
includeCategory(cat) {
|
||||
// block if category in black
|
||||
if (cat_blacklist.includes(cat)) {
|
||||
return false;
|
||||
} else {
|
||||
// if there is a whitelist, the category has to be explicitly allowed
|
||||
return cat_whitelist.length <= 0 ||
|
||||
// otherwise include if whitelist contains category
|
||||
cat_whitelist.indexOf(cat) !== -1;
|
||||
}
|
||||
},
|
||||
includeField(cat, field) {
|
||||
// column names are assumed to follow the pattern 'category_name.column_name'
|
||||
const full = cat + '.' + field;
|
||||
if (field_blacklist.includes(full)) {
|
||||
return false;
|
||||
} else {
|
||||
// if for this category no whitelist entries exist
|
||||
return !wlcatcol.includes(cat) ||
|
||||
// otherwise must be specifically allowed
|
||||
field_whitelist.includes(full);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const DefaultFilter: Filter = {
|
||||
includeCategory(cat) { return true; },
|
||||
includeField(cat, field) { return true; }
|
||||
|
||||
@@ -435,6 +435,10 @@ namespace Quat {
|
||||
|
||||
return normalize(out, Quat.fromMat3(out, axesTmpMat));
|
||||
}
|
||||
|
||||
export function toString(a: Quat, precision?: number) {
|
||||
return `[${a[0].toPrecision(precision)} ${a[1].toPrecision(precision)} ${a[2].toPrecision(precision)} ${a[3].toPrecision(precision)}]`;
|
||||
}
|
||||
}
|
||||
|
||||
export default Quat
|
||||
@@ -166,6 +166,10 @@ namespace Vec2 {
|
||||
export function areEqual(a: Vec2, b: Vec2) {
|
||||
return a[0] === b[0] && a[1] === b[1];
|
||||
}
|
||||
|
||||
export function toString(a: Vec2, precision?: number) {
|
||||
return `[${a[0].toPrecision(precision)} ${a[1].toPrecision(precision)}}]`;
|
||||
}
|
||||
}
|
||||
|
||||
export default Vec2
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
import Mat4 from './mat4';
|
||||
import { Quat, Mat3, EPSILON } from '../3d';
|
||||
import { spline as _spline, clamp } from '../../interpolate'
|
||||
import { spline as _spline, quadraticBezier as _quadraticBezier, clamp } from '../../interpolate'
|
||||
import { NumberArray } from '../../../mol-util/type-helpers';
|
||||
|
||||
interface Vec3 extends Array<number> { [d: number]: number, '@type': 'vec3', length: 3 }
|
||||
@@ -344,6 +344,14 @@ namespace Vec3 {
|
||||
return out;
|
||||
}
|
||||
|
||||
export function quadraticBezier(out: Vec3, a: Vec3, b: Vec3, c: Vec3, t: number) {
|
||||
out[0] = _quadraticBezier(a[0], b[0], c[0], t);
|
||||
out[1] = _quadraticBezier(a[1], b[1], c[1], t);
|
||||
out[2] = _quadraticBezier(a[2], b[2], c[2], t);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a spline interpolation with two control points and a tension parameter
|
||||
*/
|
||||
@@ -525,8 +533,8 @@ namespace Vec3 {
|
||||
return normalize(out, cross(out, triangleNormalTmpAB, triangleNormalTmpAC));
|
||||
}
|
||||
|
||||
export function toString(a: Vec3) {
|
||||
return `[${a[0]} ${a[1]} ${a[2]}]`;
|
||||
export function toString(a: Vec3, precision?: number) {
|
||||
return `[${a[0].toPrecision(precision)} ${a[1].toPrecision(precision)} ${a[2].toPrecision(precision)}]`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -225,6 +225,10 @@ namespace Vec4 {
|
||||
Math.abs(a2 - b2) <= EPSILON.Value * Math.max(1.0, Math.abs(a2), Math.abs(b2)) &&
|
||||
Math.abs(a3 - b3) <= EPSILON.Value * Math.max(1.0, Math.abs(a3), Math.abs(b3)));
|
||||
}
|
||||
|
||||
export function toString(a: Vec4, precision?: number) {
|
||||
return `[${a[0].toPrecision(precision)} ${a[1].toPrecision(precision)} ${a[2].toPrecision(precision)} ${a[3].toPrecision(precision)}]`;
|
||||
}
|
||||
}
|
||||
|
||||
export default Vec4
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -18,7 +18,6 @@ import { CifCategory } from '../../mol-io/reader/cif';
|
||||
import { PropertyWrapper } from '../../mol-model-props/common/wrapper';
|
||||
import { Task, RuntimeContext } from '../../mol-task';
|
||||
import { GraphQLClient } from '../../mol-util/graphql-client';
|
||||
import { ajaxGet } from '../../mol-util/data-source';
|
||||
|
||||
const { str, int, float, Aliased, Vector, List } = Column.Schema;
|
||||
|
||||
@@ -183,13 +182,12 @@ export function AssemblySymmetry(db: AssemblySymmetry.Database): AssemblySymmetr
|
||||
type SymmetryKind = 'GLOBAL' | 'LOCAL' | 'PSEUDO'
|
||||
type SymmetryType = 'ASYMMETRIC' | 'CYCLIC' | 'DIHEDRAL' | 'HELICAL' | 'ICOSAHEDRAL' | 'OCTAHEDRAL' | 'TETRAHEDRAL'
|
||||
|
||||
const Client = new GraphQLClient(AssemblySymmetry.GraphQLEndpointURL, ajaxGet)
|
||||
|
||||
export namespace AssemblySymmetry {
|
||||
export function is(x: any): x is AssemblySymmetry {
|
||||
return x['@type'] === 'rcsb_assembly_symmetry'
|
||||
}
|
||||
export const GraphQLEndpointURL = '//rest-dev.rcsb.org/graphql'
|
||||
export const GraphQLEndpointURL = '//rest-staging.rcsb.org/graphql'
|
||||
|
||||
export const Schema = {
|
||||
rcsb_assembly_symmetry_info: {
|
||||
updated_datetime_utc: Column.Schema.str
|
||||
@@ -257,7 +255,7 @@ export namespace AssemblySymmetry {
|
||||
|
||||
export const Descriptor = _Descriptor;
|
||||
|
||||
export async function attachFromCifOrAPI(model: Model, client: GraphQLClient = Client, ctx?: RuntimeContext) {
|
||||
export async function attachFromCifOrAPI(model: Model, client: GraphQLClient, ctx?: RuntimeContext) {
|
||||
if (model.customProperties.has(Descriptor)) return true;
|
||||
|
||||
let db: Database
|
||||
@@ -266,8 +264,10 @@ export namespace AssemblySymmetry {
|
||||
db = createDatabaseFromCif(model)
|
||||
} else {
|
||||
let result: AssemblySymmetryGraphQL.Query
|
||||
console.log('model.label.toLowerCase()', model.label.toLowerCase())
|
||||
const variables: AssemblySymmetryGraphQL.Variables = { pdbId: model.label.toLowerCase() };
|
||||
try {
|
||||
console.log('foo', client)
|
||||
result = await client.request<AssemblySymmetryGraphQL.Query>(ctx || RuntimeContext.Synchronous, query, variables);
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
||||
84
src/mol-model-props/wwpdb/chem-comp-bond.ts
Normal file
84
src/mol-model-props/wwpdb/chem-comp-bond.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Column, Table } from '../../mol-data/db';
|
||||
import { toTable } from '../../mol-io/reader/cif/schema';
|
||||
import { Model, CustomPropertyDescriptor } from '../../mol-model/structure';
|
||||
import { mmCIF_chemCompBond_schema } from '../../mol-io/reader/cif/schema/mmcif-extras';
|
||||
import { CifWriter } from '../../mol-io/writer/cif';
|
||||
|
||||
export namespace ChemCompBond {
|
||||
export type Property = Table<Schema['chem_comp_bond']>
|
||||
|
||||
export function getFromModel(model: Model): Property {
|
||||
if (model.sourceData.kind !== 'mmCIF') return Table.ofUndefinedColumns(Schema.chem_comp_bond, 0);
|
||||
const { chem_comp_bond } = model.sourceData.data
|
||||
return Table.ofColumns(Schema.chem_comp_bond, {
|
||||
...chem_comp_bond,
|
||||
molstar_protonation_variant: Column.Undefined(chem_comp_bond._rowCount, Column.Schema.Str())
|
||||
});
|
||||
}
|
||||
|
||||
export function get(model: Model): Property {
|
||||
return model._staticPropertyData.__ChemCompBond__ || getFromModel(model);
|
||||
}
|
||||
function set(model: Model, prop: Property) {
|
||||
(model._staticPropertyData.__ChemCompBond__ as Property) = prop;
|
||||
}
|
||||
|
||||
export const Schema = { chem_comp_bond: mmCIF_chemCompBond_schema };
|
||||
export type Schema = typeof Schema
|
||||
|
||||
export const Descriptor = CustomPropertyDescriptor({
|
||||
isStatic: true,
|
||||
name: 'chem_comp_bond',
|
||||
cifExport: {
|
||||
prefix: '',
|
||||
context(ctx): Property { return get(ctx.firstModel); },
|
||||
categories: [{
|
||||
name: 'chem_comp_bond',
|
||||
instance(ctx: Property) {
|
||||
return CifWriter.Category.ofTable(ctx);
|
||||
}
|
||||
}]
|
||||
}
|
||||
});
|
||||
|
||||
function fromCifData(model: Model): Table<Schema['chem_comp_bond']> | undefined {
|
||||
if (model.sourceData.kind !== 'mmCIF') return void 0;
|
||||
const cat = model.sourceData.frame.categories.chem_comp_bond;
|
||||
if (!cat) return void 0;
|
||||
return toTable(Schema.chem_comp_bond, cat);
|
||||
}
|
||||
|
||||
export async function attachFromCifOrTable(model: Model, params: {
|
||||
// optional Table source
|
||||
wwPDB_apiSourceTable?: (model: Model) => Promise<Table<Schema['chem_comp_bond']>>
|
||||
}) {
|
||||
if (model.customProperties.has(Descriptor)) return true;
|
||||
|
||||
let chemCompBond: Table<Schema['chem_comp_bond']> | undefined = fromCifData(model);
|
||||
if (chemCompBond === void 0 && params.wwPDB_apiSourceTable) {
|
||||
const data = await params.wwPDB_apiSourceTable(model);
|
||||
if (!data) return false;
|
||||
chemCompBond = chemCompBondFromTable(model, data);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!chemCompBond) return false;
|
||||
|
||||
model.customProperties.add(Descriptor);
|
||||
set(model, chemCompBond);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function chemCompBondFromTable(model: Model, table: Table<ChemCompBond.Schema['chem_comp_bond']>): Table<ChemCompBond.Schema['chem_comp_bond']> {
|
||||
return Table.pick(table, ChemCompBond.Schema.chem_comp_bond, (i: number) => {
|
||||
return model.properties.chemicalComponentMap.has(table.comp_id.value(i))
|
||||
})
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 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>
|
||||
@@ -113,7 +113,8 @@ export interface AtomicData {
|
||||
export interface AtomicDerivedData {
|
||||
readonly residue: {
|
||||
readonly traceElementIndex: ArrayLike<ElementIndex | -1>
|
||||
readonly directionElementIndex: ArrayLike<ElementIndex | -1>
|
||||
readonly directionFromElementIndex: ArrayLike<ElementIndex | -1>
|
||||
readonly directionToElementIndex: ArrayLike<ElementIndex | -1>
|
||||
readonly moleculeType: ArrayLike<MoleculeType>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ export function getAtomicDerivedData(data: AtomicData, index: AtomicIndex, chemi
|
||||
const { label_comp_id, _rowCount: n } = data.residues
|
||||
|
||||
const traceElementIndex = new Int32Array(n)
|
||||
const directionElementIndex = new Int32Array(n)
|
||||
const directionFromElementIndex = new Int32Array(n)
|
||||
const directionToElementIndex = new Int32Array(n)
|
||||
const moleculeType = new Uint8Array(n)
|
||||
|
||||
const moleculeTypeMap = new Map<string, MoleculeType>()
|
||||
@@ -44,14 +45,18 @@ export function getAtomicDerivedData(data: AtomicData, index: AtomicIndex, chemi
|
||||
}
|
||||
traceElementIndex[i] = traceIndex
|
||||
|
||||
const directionAtomId = getAtomIdForAtomRole(molType, 'direction')
|
||||
directionElementIndex[i] = index.findAtomsOnResidue(i as ResidueIndex, directionAtomId)
|
||||
const directionFromAtomId = getAtomIdForAtomRole(molType, 'directionFrom')
|
||||
directionFromElementIndex[i] = index.findAtomsOnResidue(i as ResidueIndex, directionFromAtomId)
|
||||
|
||||
const directionToAtomId = getAtomIdForAtomRole(molType, 'directionTo')
|
||||
directionToElementIndex[i] = index.findAtomsOnResidue(i as ResidueIndex, directionToAtomId)
|
||||
}
|
||||
|
||||
return {
|
||||
residue: {
|
||||
traceElementIndex: traceElementIndex as unknown as ArrayLike<ElementIndex | -1>,
|
||||
directionElementIndex: directionElementIndex as unknown as ArrayLike<ElementIndex | -1>,
|
||||
directionFromElementIndex: directionFromElementIndex as unknown as ArrayLike<ElementIndex | -1>,
|
||||
directionToElementIndex: directionToElementIndex as unknown as ArrayLike<ElementIndex | -1>,
|
||||
moleculeType: moleculeType as unknown as ArrayLike<MoleculeType>,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,33 +59,39 @@ export const enum MoleculeType {
|
||||
saccharide
|
||||
}
|
||||
|
||||
export type AtomRole = 'trace' | 'direction' | 'backboneStart' | 'backboneEnd' | 'coarseBackbone'
|
||||
export type AtomRole = 'trace' | 'directionFrom' | 'directionTo' | 'backboneStart' | 'backboneEnd' | 'coarseBackbone'
|
||||
|
||||
export const MoleculeTypeAtomRoleId: { [k: number]: { [k in AtomRole]: Set<string> } } = {
|
||||
[MoleculeType.protein]: {
|
||||
trace: new Set(['CA']),
|
||||
direction: new Set(['O', 'OC1', 'O1', 'OX1', 'OXT']),
|
||||
directionFrom: new Set(['C']),
|
||||
directionTo: new Set(['O', 'OC1', 'O1', 'OX1', 'OXT']),
|
||||
backboneStart: new Set(['N']),
|
||||
backboneEnd: new Set(['C']),
|
||||
coarseBackbone: new Set(['CA', 'BB'])
|
||||
// CA1 is used e.g. in GFP chromophores
|
||||
// BB is often used for coarse grained models
|
||||
coarseBackbone: new Set(['CA', 'BB', 'CA1'])
|
||||
},
|
||||
[MoleculeType.RNA]: {
|
||||
trace: new Set(['C4\'', 'C4*']),
|
||||
direction: new Set(['C3\'', 'C3*']),
|
||||
directionFrom: new Set(['C4\'', 'C4*']),
|
||||
directionTo: new Set(['C3\'', 'C3*']),
|
||||
backboneStart: new Set(['P']),
|
||||
backboneEnd: new Set(['O3\'', 'O3*']),
|
||||
coarseBackbone: new Set(['P'])
|
||||
},
|
||||
[MoleculeType.DNA]: {
|
||||
trace: new Set(['C3\'', 'C3*']),
|
||||
direction: new Set(['C1\'', 'C1*']),
|
||||
directionFrom: new Set(['C3\'', 'C3*']),
|
||||
directionTo: new Set(['C1\'', 'C1*']),
|
||||
backboneStart: new Set(['P']),
|
||||
backboneEnd: new Set(['O3\'', 'O3*']),
|
||||
coarseBackbone: new Set(['P'])
|
||||
},
|
||||
[MoleculeType.PNA]: {
|
||||
trace: new Set(['N4\'', 'N4*']),
|
||||
direction: new Set(['C7\'', 'C7*']),
|
||||
directionFrom: new Set(['N4\'', 'N4*']),
|
||||
directionTo: new Set(['C7\'', 'C7*']),
|
||||
backboneStart: new Set(['N1\'', 'N1*']),
|
||||
backboneEnd: new Set(['C\'', 'C*']),
|
||||
coarseBackbone: new Set(['P'])
|
||||
|
||||
@@ -507,6 +507,11 @@ namespace Structure {
|
||||
)
|
||||
}
|
||||
|
||||
/** Check if the structures or their parents are equivalent */
|
||||
export function areParentsEquivalent(a: Structure, b: Structure) {
|
||||
return areEquivalent(a.parent || a, b.parent || b)
|
||||
}
|
||||
|
||||
export class ElementLocationIterator implements Iterator<StructureElement> {
|
||||
private current = StructureElement.create();
|
||||
private unitIndex = 0;
|
||||
|
||||
@@ -43,13 +43,13 @@ export namespace VolumeStreaming {
|
||||
valuesInfo: [{ mean: 0, min: -1, max: 1, sigma: 0.1 }, { mean: 0, min: -1, max: 1, sigma: 0.1 }]
|
||||
};
|
||||
|
||||
export function createParams(data?: VolumeServerInfo.Data) {
|
||||
export function createParams(data?: VolumeServerInfo.Data, defaultView?: ViewTypes) {
|
||||
// fake the info
|
||||
const info = data || { kind: 'em', header: { sampling: [fakeSampling], availablePrecisions: [{ precision: 0, maxVoxels: 0 }] }, emDefaultContourLevel: VolumeIsoValue.relative(0) };
|
||||
const box = (data && data.structure.boundary.box) || Box3D.empty();
|
||||
|
||||
return {
|
||||
view: PD.MappedStatic(info.kind === 'em' ? 'cell' : 'selection-box', {
|
||||
view: PD.MappedStatic(defaultView || (info.kind === 'em' ? 'cell' : 'selection-box'), {
|
||||
'box': PD.Group({
|
||||
bottomLeft: PD.Vec3(box.min),
|
||||
topRight: PD.Vec3(box.max),
|
||||
@@ -76,6 +76,8 @@ export namespace VolumeStreaming {
|
||||
};
|
||||
}
|
||||
|
||||
export type ViewTypes = 'box' | 'selection-box' | 'cell'
|
||||
|
||||
export type ParamDefinition = typeof createParams extends (...args: any[]) => (infer T) ? T : never
|
||||
export type Params = ParamDefinition extends PD.Params ? PD.Values<ParamDefinition> : {}
|
||||
|
||||
|
||||
@@ -26,10 +26,13 @@ export const InitVolumeStreaming = StateAction.build({
|
||||
display: { name: 'Volume Streaming' },
|
||||
from: SO.Molecule.Structure,
|
||||
params(a) {
|
||||
const method = getStreamingMethod(a && a.data);
|
||||
return {
|
||||
method: PD.Select<VolumeServerInfo.Kind>(getStreamingMethod(a && a.data), [['em', 'EM'], ['x-ray', 'X-Ray']]),
|
||||
method: PD.Select<VolumeServerInfo.Kind>(method, [['em', 'EM'], ['x-ray', 'X-Ray']]),
|
||||
id: PD.Text((a && a.data.models.length > 0 && a.data.models[0].label) || ''),
|
||||
serverUrl: PD.Text('https://ds.litemol.org')
|
||||
serverUrl: PD.Text('https://ds.litemol.org'),
|
||||
defaultView: PD.Text<VolumeStreaming.ViewTypes>(method === 'em' ? 'cell' : 'selection-box'),
|
||||
behaviorRef: PD.Text('', { isHidden: true })
|
||||
};
|
||||
},
|
||||
isApplicable: (a) => a.data.models.length === 1
|
||||
@@ -56,7 +59,8 @@ export const InitVolumeStreaming = StateAction.build({
|
||||
const infoObj = await state.updateTree(infoTree).runInContext(taskCtx);
|
||||
|
||||
const behTree = state.build().to(infoTree.ref).apply(CreateVolumeStreamingBehavior,
|
||||
PD.getDefaultValues(VolumeStreaming.createParams(infoObj.data)));
|
||||
PD.getDefaultValues(VolumeStreaming.createParams(infoObj.data, params.defaultView)),
|
||||
{ ref: params.behaviorRef ? params.behaviorRef : void 0 });
|
||||
|
||||
if (params.method === 'em') {
|
||||
behTree.apply(VolumeStreamingVisual, { channel: 'em' }, { state: { isGhost: true } });
|
||||
|
||||
@@ -73,7 +73,7 @@ export class PluginContext {
|
||||
isUpdating: this.ev.behavior<boolean>(false)
|
||||
},
|
||||
canvas3d: {
|
||||
highlight: this.ev.behavior<Canvas3D.HighlightEvent>({ current: Representation.Loci.Empty, prev: Representation.Loci.Empty }),
|
||||
highlight: this.ev.behavior<Canvas3D.HighlightEvent>({ current: Representation.Loci.Empty }),
|
||||
click: this.ev.behavior<Canvas3D.ClickEvent>({ current: Representation.Loci.Empty, modifiers: ModifiersKeys.None, buttons: 0 })
|
||||
},
|
||||
labels: {
|
||||
|
||||
@@ -77,7 +77,10 @@ export const DefaultPluginSpec: PluginSpec = {
|
||||
AnimateAssemblyUnwind,
|
||||
AnimateUnitsExplode,
|
||||
AnimateStateInterpolation
|
||||
]
|
||||
],
|
||||
layout: {
|
||||
controls: { top: 'none' }
|
||||
}
|
||||
}
|
||||
|
||||
export function createPlugin(target: HTMLElement, spec?: PluginSpec): PluginContext {
|
||||
|
||||
@@ -4,17 +4,17 @@
|
||||
background: $default-background;
|
||||
text-align: center;
|
||||
//font-style: italic;
|
||||
|
||||
-webkit-user-select: none; /* Chrome/Safari */
|
||||
|
||||
-webkit-user-select: none; /* Chrome/Safari */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* IE10+ */
|
||||
|
||||
/* Rules below not implemented in browsers yet */
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
|
||||
font-weight: light;
|
||||
|
||||
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@@ -34,10 +34,10 @@
|
||||
top: 0;
|
||||
display: table;
|
||||
text-align: center;
|
||||
|
||||
|
||||
> div {
|
||||
b {
|
||||
font-size: 120%;
|
||||
font-size: 120%;
|
||||
}
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
@@ -68,6 +68,6 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.msp-contols-section {
|
||||
.msp-controls-section {
|
||||
margin-bottom: $control-spacing;
|
||||
}
|
||||
@@ -33,34 +33,34 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.msp-layout-main, .msp-layout-bottom {
|
||||
.msp-layout-static {
|
||||
.msp-layout-top, .msp-layout-main, .msp-layout-bottom {
|
||||
.msp-layout-static {
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.msp-layout-right {
|
||||
.msp-layout-static {
|
||||
.msp-layout-right {
|
||||
.msp-layout-static {
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0; // height: $row-height + $control-spacing;
|
||||
}
|
||||
|
||||
|
||||
.msp-layout-scrollable {
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: $row-height + $control-spacing + 1;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.msp-layout-left {
|
||||
.msp-layout-static {
|
||||
.msp-layout-left {
|
||||
.msp-layout-static {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 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 { StateTransformer, StateAction } from '../mol-state';
|
||||
@@ -18,11 +19,7 @@ interface PluginSpec {
|
||||
customParamEditors?: [StateAction | StateTransformer, StateTransformParameters.Class][],
|
||||
layout?: {
|
||||
initial?: Partial<PluginLayoutStateProps>,
|
||||
controls?: {
|
||||
left?: React.ComponentClass | 'none',
|
||||
right?: React.ComponentClass | 'none',
|
||||
bottom?: React.ComponentClass | 'none'
|
||||
}
|
||||
controls?: PluginSpec.LayoutControls
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +44,7 @@ namespace PluginSpec {
|
||||
}
|
||||
|
||||
export interface LayoutControls {
|
||||
top?: React.ComponentClass | 'none',
|
||||
left?: React.ComponentClass | 'none',
|
||||
right?: React.ComponentClass | 'none',
|
||||
bottom?: React.ComponentClass | 'none'
|
||||
|
||||
@@ -15,7 +15,7 @@ import { camelCaseToWords } from '../../../mol-util/string';
|
||||
import * as React from 'react';
|
||||
import LineGraphComponent from './line-graph/line-graph-component';
|
||||
import { Slider, Slider2 } from './slider';
|
||||
import { NumericInput, IconButton } from './common';
|
||||
import { NumericInput, IconButton, ControlGroup } from './common';
|
||||
import { _Props, _State } from '../base';
|
||||
|
||||
export interface ParameterControlsProps<P extends PD.Params = PD.Params> {
|
||||
@@ -97,7 +97,7 @@ export abstract class SimpleParam<P extends PD.Any> extends React.PureComponent<
|
||||
}
|
||||
}
|
||||
|
||||
export class BoolControl extends SimpleParam<PD.Boolean> {
|
||||
export class BoolControl extends SimpleParam<PD.BooleanParam> {
|
||||
onClick = (e: React.MouseEvent<HTMLButtonElement>) => { this.update(!this.props.value); e.currentTarget.blur(); }
|
||||
renderControl() {
|
||||
return <button onClick={this.onClick} disabled={this.props.isDisabled}>
|
||||
@@ -461,8 +461,7 @@ export class GroupControl extends React.PureComponent<ParamProps<PD.Group<any>>,
|
||||
</div>
|
||||
{this.state.isExpanded && <div className='msp-control-offset' style={{ display: this.state.isExpanded ? 'block' : 'none' }}>
|
||||
{controls}
|
||||
</div>
|
||||
}
|
||||
</div>}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -655,9 +654,12 @@ export class ObjectListControl extends React.PureComponent<ParamProps<PD.ObjectL
|
||||
<button onClick={this.toggleExpanded}>{value}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{this.state.isExpanded && <div className='msp-control-offset'>
|
||||
{this.props.value.map((v, i) => <ObjectListItem key={i} param={this.props.param} value={v} index={i} actions={this.actions} />)}
|
||||
<ObjectListEditor params={this.props.param.element} apply={this.add} value={this.props.param.ctor()} isDisabled={this.props.isDisabled} />
|
||||
<ControlGroup header='New Item'>
|
||||
<ObjectListEditor params={this.props.param.element} apply={this.add} value={this.props.param.ctor()} isDisabled={this.props.isDisabled} />
|
||||
</ControlGroup>
|
||||
</div>}
|
||||
</>;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 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 { List } from 'immutable';
|
||||
@@ -18,7 +19,7 @@ import { StateTree } from './state/tree';
|
||||
import { BackgroundTaskProgress } from './task';
|
||||
import { Viewport, ViewportControls } from './viewport';
|
||||
import { StateTransform } from '../../mol-state';
|
||||
import { UpdateTransformContol } from './state/update-transform';
|
||||
import { UpdateTransformControl } from './state/update-transform';
|
||||
|
||||
export class Plugin extends React.Component<{ plugin: PluginContext }, {}> {
|
||||
|
||||
@@ -37,27 +38,61 @@ export class Plugin extends React.Component<{ plugin: PluginContext }, {}> {
|
||||
}
|
||||
}
|
||||
|
||||
export class PluginContextContainer extends React.Component<{ plugin: PluginContext }> {
|
||||
render() {
|
||||
return <PluginReactContext.Provider value={this.props.plugin}>
|
||||
<div className='msp-plugin'>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</PluginReactContext.Provider>;
|
||||
}
|
||||
}
|
||||
|
||||
type RegionKind = 'top' | 'left' | 'right' | 'bottom' | 'main'
|
||||
|
||||
class Layout extends PluginUIComponent {
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.layout.events.updated, () => this.forceUpdate());
|
||||
}
|
||||
|
||||
region(kind: 'left' | 'right' | 'bottom' | 'main', Element: React.ComponentClass) {
|
||||
region(kind: RegionKind, Element?: React.ComponentClass) {
|
||||
return <div className={`msp-layout-region msp-layout-${kind}`}>
|
||||
<div className='msp-layout-static'>
|
||||
<Element />
|
||||
{Element ? <Element /> : null}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
get layoutVisibilityClassName() {
|
||||
const layout = this.plugin.layout.state;
|
||||
const controls = (this.plugin.spec.layout && this.plugin.spec.layout.controls) || { };
|
||||
|
||||
const classList: string[] = []
|
||||
if (controls.top === 'none' || !layout.showControls) {
|
||||
classList.push('msp-layout-hide-top')
|
||||
}
|
||||
if (controls.left === 'none' || !layout.showControls) {
|
||||
classList.push('msp-layout-hide-left')
|
||||
}
|
||||
if (controls.right === 'none' || !layout.showControls) {
|
||||
classList.push('msp-layout-hide-right')
|
||||
}
|
||||
if (controls.bottom === 'none' || !layout.showControls) {
|
||||
classList.push('msp-layout-hide-bottom')
|
||||
}
|
||||
|
||||
return classList.join(' ')
|
||||
}
|
||||
|
||||
render() {
|
||||
const layout = this.plugin.layout.state;
|
||||
const controls = (this.plugin.spec.layout && this.plugin.spec.layout.controls) || { };
|
||||
|
||||
return <div className='msp-plugin'>
|
||||
<div className={`msp-plugin-content ${layout.isExpanded ? 'msp-layout-expanded' : 'msp-layout-standard msp-layout-standard-outside'}`}>
|
||||
<div className={layout.showControls ? 'msp-layout-hide-top' : 'msp-layout-hide-top msp-layout-hide-right msp-layout-hide-bottom msp-layout-hide-left'}>
|
||||
<div className={this.layoutVisibilityClassName}>
|
||||
{this.region('main', ViewportWrapper)}
|
||||
{layout.showControls && controls.top !== 'none' && this.region('top', controls.top)}
|
||||
{layout.showControls && controls.left !== 'none' && this.region('left', controls.left || State)}
|
||||
{layout.showControls && controls.right !== 'none' && this.region('right', controls.right || ControlsWrapper)}
|
||||
{layout.showControls && controls.bottom !== 'none' && this.region('bottom', controls.bottom || Log)}
|
||||
@@ -67,7 +102,6 @@ class Layout extends PluginUIComponent {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ControlsWrapper extends PluginUIComponent {
|
||||
render() {
|
||||
return <div className='msp-scrollable-container msp-right-controls'>
|
||||
@@ -193,7 +227,7 @@ export class CurrentObject extends PluginUIComponent {
|
||||
if (!showActions) return null;
|
||||
|
||||
return <>
|
||||
{(cell.status === 'ok' || cell.status === 'error') && <UpdateTransformContol state={current.state} transform={transform} /> }
|
||||
{(cell.status === 'ok' || cell.status === 'error') && <UpdateTransformControl state={current.state} transform={transform} /> }
|
||||
{cell.status === 'ok' && <StateObjectActions state={current.state} nodeRef={ref} initiallyColapsed />}
|
||||
</>;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import { PluginUIComponent } from '../base';
|
||||
import { ApplyActionContol } from './apply-action';
|
||||
import { ApplyActionControl } from './apply-action';
|
||||
import { State } from '../../../mol-state';
|
||||
import { Icon } from '../controls/common';
|
||||
|
||||
@@ -38,7 +38,7 @@ export class StateObjectActions extends PluginUIComponent<{ state: State, nodeRe
|
||||
|
||||
return <div className='msp-state-actions'>
|
||||
{!this.props.hideHeader && <div className='msp-section-header'><Icon name='code' /> {`Actions (${display})`}</div> }
|
||||
{actions.map((act, i) => <ApplyActionContol plugin={this.plugin} key={`${act.id}`} state={state} action={act} nodeRef={ref} initiallyCollapsed={this.props.initiallyColapsed} />)}
|
||||
{actions.map((act, i) => <ApplyActionControl plugin={this.plugin} key={`${act.id}`} state={state} action={act} nodeRef={ref} initiallyCollapsed={this.props.initiallyColapsed} />)}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ export class AnimationControlsWrapper extends PluginUIComponent<{ }> {
|
||||
render() {
|
||||
const anim = this.plugin.state.animation;
|
||||
if (anim.isEmpty) return null;
|
||||
return <div className='msp-contols-section'>
|
||||
return <div className='msp-controls-section'>
|
||||
<div className='msp-section-header'><Icon name='code' /> Animations</div>
|
||||
<AnimationControls />
|
||||
</div>
|
||||
|
||||
@@ -8,12 +8,12 @@ import { PluginCommands } from '../../../mol-plugin/command';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { State, StateTransform, StateAction } from '../../../mol-state';
|
||||
import { memoizeLatest } from '../../../mol-util/memoize';
|
||||
import { StateTransformParameters, TransformContolBase } from './common';
|
||||
import { StateTransformParameters, TransformControlBase } from './common';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
|
||||
export { ApplyActionContol };
|
||||
export { ApplyActionControl };
|
||||
|
||||
namespace ApplyActionContol {
|
||||
namespace ApplyActionControl {
|
||||
export interface Props {
|
||||
plugin: PluginContext,
|
||||
nodeRef: StateTransform.Ref,
|
||||
@@ -32,7 +32,7 @@ namespace ApplyActionContol {
|
||||
}
|
||||
}
|
||||
|
||||
class ApplyActionContol extends TransformContolBase<ApplyActionContol.Props, ApplyActionContol.ComponentState> {
|
||||
class ApplyActionControl extends TransformControlBase<ApplyActionControl.Props, ApplyActionControl.ComponentState> {
|
||||
applyAction() {
|
||||
return PluginCommands.State.ApplyAction.dispatch(this.plugin, {
|
||||
state: this.props.state,
|
||||
@@ -53,7 +53,7 @@ class ApplyActionContol extends TransformContolBase<ApplyActionContol.Props, App
|
||||
|
||||
state = { ref: this.props.nodeRef, version: this.props.state.transforms.get(this.props.nodeRef).version, error: void 0, isInitial: true, params: this.getInfo().initialValues, busy: false, isCollapsed: this.props.initiallyCollapsed };
|
||||
|
||||
static getDerivedStateFromProps(props: ApplyActionContol.Props, state: ApplyActionContol.ComponentState) {
|
||||
static getDerivedStateFromProps(props: ApplyActionControl.Props, state: ApplyActionControl.ComponentState) {
|
||||
if (props.nodeRef === state.ref) return null;
|
||||
const version = props.state.transforms.get(props.nodeRef).version;
|
||||
if (version === state.version) return null;
|
||||
@@ -63,7 +63,7 @@ class ApplyActionContol extends TransformContolBase<ApplyActionContol.Props, App
|
||||
? PD.getDefaultValues(props.action.definition.params(source, props.plugin))
|
||||
: { };
|
||||
|
||||
const newState: Partial<ApplyActionContol.ComponentState> = {
|
||||
const newState: Partial<ApplyActionControl.ComponentState> = {
|
||||
ref: props.nodeRef,
|
||||
version,
|
||||
params,
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { Subject } from 'rxjs';
|
||||
import { Icon } from '../controls/common';
|
||||
|
||||
export { StateTransformParameters, TransformContolBase };
|
||||
export { StateTransformParameters, TransformControlBase };
|
||||
|
||||
class StateTransformParameters extends PurePluginUIComponent<StateTransformParameters.Props> {
|
||||
validate(params: any) {
|
||||
@@ -89,7 +89,7 @@ namespace StateTransformParameters {
|
||||
}
|
||||
}
|
||||
|
||||
namespace TransformContolBase {
|
||||
namespace TransformControlBase {
|
||||
export interface ComponentState {
|
||||
params: any,
|
||||
error?: string,
|
||||
@@ -99,7 +99,7 @@ namespace TransformContolBase {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TransformContolBase<P, S extends TransformContolBase.ComponentState> extends PurePluginUIComponent<P, S> {
|
||||
abstract class TransformControlBase<P, S extends TransformControlBase.ComponentState> extends PurePluginUIComponent<P, S> {
|
||||
abstract applyAction(): Promise<void>;
|
||||
abstract getInfo(): StateTransformParameters.Props['info'];
|
||||
abstract getHeader(): StateTransformer.Definition['display'];
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
|
||||
import { State, StateTransform, StateTransformer } from '../../../mol-state';
|
||||
import { memoizeLatest } from '../../../mol-util/memoize';
|
||||
import { StateTransformParameters, TransformContolBase } from './common';
|
||||
import { StateTransformParameters, TransformControlBase } from './common';
|
||||
import { Observable } from 'rxjs';
|
||||
import * as React from 'react';
|
||||
import { PluginUIComponent } from '../base';
|
||||
|
||||
export { UpdateTransformContol, TransformUpdaterControl };
|
||||
export { UpdateTransformControl, TransformUpdaterControl };
|
||||
|
||||
namespace UpdateTransformContol {
|
||||
namespace UpdateTransformControl {
|
||||
export interface Props {
|
||||
transform: StateTransform,
|
||||
state: State,
|
||||
@@ -22,12 +22,12 @@ namespace UpdateTransformContol {
|
||||
customHeader?: StateTransformer.Definition['display']
|
||||
}
|
||||
|
||||
export interface ComponentState extends TransformContolBase.ComponentState {
|
||||
export interface ComponentState extends TransformControlBase.ComponentState {
|
||||
transform: StateTransform
|
||||
}
|
||||
}
|
||||
|
||||
class UpdateTransformContol extends TransformContolBase<UpdateTransformContol.Props, UpdateTransformContol.ComponentState> {
|
||||
class UpdateTransformControl extends TransformControlBase<UpdateTransformControl.Props, UpdateTransformControl.ComponentState> {
|
||||
applyAction() { return this.plugin.updateTransform(this.props.state, this.props.transform.ref, this.state.params); }
|
||||
getInfo() { return this._getInfo(this.props.transform); }
|
||||
getTransformerId() { return this.props.transform.transformer.id; }
|
||||
@@ -70,12 +70,12 @@ class UpdateTransformContol extends TransformContolBase<UpdateTransformContol.Pr
|
||||
|
||||
private _getInfo = memoizeLatest((t: StateTransform) => StateTransformParameters.infoFromTransform(this.plugin, this.props.state, t));
|
||||
|
||||
state: UpdateTransformContol.ComponentState = { transform: this.props.transform, error: void 0, isInitial: true, params: this.getInfo().initialValues, busy: false, isCollapsed: this.props.initiallyCollapsed };
|
||||
state: UpdateTransformControl.ComponentState = { transform: this.props.transform, error: void 0, isInitial: true, params: this.getInfo().initialValues, busy: false, isCollapsed: this.props.initiallyCollapsed };
|
||||
|
||||
static getDerivedStateFromProps(props: UpdateTransformContol.Props, state: UpdateTransformContol.ComponentState) {
|
||||
static getDerivedStateFromProps(props: UpdateTransformControl.Props, state: UpdateTransformControl.ComponentState) {
|
||||
if (props.transform === state.transform) return null;
|
||||
const cell = props.state.cells.get(props.transform.ref)!;
|
||||
const newState: Partial<UpdateTransformContol.ComponentState> = {
|
||||
const newState: Partial<UpdateTransformControl.ComponentState> = {
|
||||
transform: props.transform,
|
||||
params: (cell.params && cell.params.values) || { },
|
||||
isInitial: true,
|
||||
@@ -101,7 +101,6 @@ class TransformUpdaterControl extends PluginUIComponent<{ nodeRef: string, initi
|
||||
if (!cell || (cell.status !== 'ok' && cell.status !== 'error')) return null;
|
||||
|
||||
const transform = cell.transform;
|
||||
|
||||
return <UpdateTransformContol state={state} transform={transform} initiallyCollapsed={this.props.initiallyCollapsed} customHeader={this.props.header} />;
|
||||
return <UpdateTransformControl state={state} transform={transform} initiallyCollapsed={this.props.initiallyCollapsed} customHeader={this.props.header} />;
|
||||
}
|
||||
}
|
||||
@@ -165,7 +165,7 @@ export function ComplexVisual<G extends Geometry, P extends ComplexParams & Geom
|
||||
}
|
||||
|
||||
function lociApply(loci: Loci, apply: (interval: Interval) => boolean) {
|
||||
if (isEveryLoci(loci) || (Structure.isLoci(loci) && Structure.areEquivalent(loci.structure, currentStructure))) {
|
||||
if (isEveryLoci(loci) || (Structure.isLoci(loci) && Structure.areParentsEquivalent(loci.structure, currentStructure))) {
|
||||
return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount))
|
||||
} else {
|
||||
return eachLocation(loci, currentStructure, apply)
|
||||
|
||||
@@ -214,7 +214,7 @@ export function UnitsVisual<G extends Geometry, P extends UnitsParams & Geometry
|
||||
}
|
||||
|
||||
function lociApply(loci: Loci, apply: (interval: Interval) => boolean) {
|
||||
if (isEveryLoci(loci) || (Structure.isLoci(loci) && Structure.areEquivalent(loci.structure, currentStructureGroup.structure))) {
|
||||
if (isEveryLoci(loci) || (Structure.isLoci(loci) && Structure.areParentsEquivalent(loci.structure, currentStructureGroup.structure))) {
|
||||
return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount))
|
||||
} else {
|
||||
return eachLocation(loci, currentStructureGroup, apply)
|
||||
|
||||
@@ -119,7 +119,7 @@ function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) {
|
||||
function eachCarbohydrateLink(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) {
|
||||
let changed = false
|
||||
if (Link.isLoci(loci)) {
|
||||
if (!Structure.areEquivalent(loci.structure, structure)) return false
|
||||
if (!Structure.areParentsEquivalent(loci.structure, structure)) return false
|
||||
const { getLinkIndex } = structure.carbohydrates
|
||||
for (const l of loci.links) {
|
||||
const idx = getLinkIndex(l.aUnit, l.aUnit.elements[l.aIndex], l.bUnit, l.bUnit.elements[l.bIndex])
|
||||
@@ -128,7 +128,7 @@ function eachCarbohydrateLink(loci: Loci, structure: Structure, apply: (interval
|
||||
}
|
||||
}
|
||||
} else if (StructureElement.isLoci(loci)) {
|
||||
if (!Structure.areEquivalent(loci.structure, structure)) return false
|
||||
if (!Structure.areParentsEquivalent(loci.structure, structure)) return false
|
||||
// TODO mark link only when both of the link elements are in a StructureElement.Loci
|
||||
const { getElementIndex, getLinkIndices, elements } = structure.carbohydrates
|
||||
for (const e of loci.elements) {
|
||||
|
||||
@@ -196,7 +196,7 @@ function eachCarbohydrate(loci: Loci, structure: Structure, apply: (interval: In
|
||||
const { getElementIndex, getAnomericCarbons } = structure.carbohydrates
|
||||
let changed = false
|
||||
if (!StructureElement.isLoci(loci)) return false
|
||||
if (!Structure.areEquivalent(loci.structure, structure)) return false
|
||||
if (!Structure.areParentsEquivalent(loci.structure, structure)) return false
|
||||
for (const e of loci.elements) {
|
||||
// TODO make more efficient by handling/grouping `e.indices` by residue index
|
||||
// TODO only call apply when the full alt-residue of the unit is part of `e`
|
||||
|
||||
@@ -133,7 +133,7 @@ function eachTerminalLink(loci: Loci, structure: Structure, apply: (interval: In
|
||||
const { getTerminalLinkIndex } = structure.carbohydrates
|
||||
let changed = false
|
||||
if (Link.isLoci(loci)) {
|
||||
if (!Structure.areEquivalent(loci.structure, structure)) return false
|
||||
if (!Structure.areParentsEquivalent(loci.structure, structure)) return false
|
||||
for (const l of loci.links) {
|
||||
const idx = getTerminalLinkIndex(l.aUnit, l.aUnit.elements[l.aIndex], l.bUnit, l.bUnit.elements[l.bIndex])
|
||||
if (idx !== undefined) {
|
||||
@@ -141,7 +141,7 @@ function eachTerminalLink(loci: Loci, structure: Structure, apply: (interval: In
|
||||
}
|
||||
}
|
||||
} else if (StructureElement.isLoci(loci)) {
|
||||
if (!Structure.areEquivalent(loci.structure, structure)) return false
|
||||
if (!Structure.areParentsEquivalent(loci.structure, structure)) return false
|
||||
// TODO mark link only when both of the link elements are in a StructureElement.Loci
|
||||
const { getElementIndex, getTerminalLinkIndices, elements } = structure.carbohydrates
|
||||
for (const e of loci.elements) {
|
||||
|
||||
@@ -107,7 +107,7 @@ function eachCrossLink(loci: Loci, structure: Structure, apply: (interval: Inter
|
||||
const crossLinks = structure.crossLinkRestraints
|
||||
let changed = false
|
||||
if (Link.isLoci(loci)) {
|
||||
if (!Structure.areEquivalent(loci.structure, structure)) return false
|
||||
if (!Structure.areParentsEquivalent(loci.structure, structure)) return false
|
||||
for (const b of loci.links) {
|
||||
const indices = crossLinks.getPairIndices(b.aIndex, b.aUnit, b.bIndex, b.bUnit)
|
||||
if (indices) {
|
||||
|
||||
@@ -97,7 +97,7 @@ function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) {
|
||||
function eachLink(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) {
|
||||
let changed = false
|
||||
if (Link.isLoci(loci)) {
|
||||
if (!Structure.areEquivalent(loci.structure, structure)) return false
|
||||
if (!Structure.areParentsEquivalent(loci.structure, structure)) return false
|
||||
for (const b of loci.links) {
|
||||
const idx = structure.links.getBondIndex(b.aIndex, b.aUnit, b.bIndex, b.bUnit)
|
||||
if (idx !== -1) {
|
||||
@@ -105,7 +105,7 @@ function eachLink(loci: Loci, structure: Structure, apply: (interval: Interval)
|
||||
}
|
||||
}
|
||||
} else if (StructureElement.isLoci(loci)) {
|
||||
if (!Structure.areEquivalent(loci.structure, structure)) return false
|
||||
if (!Structure.areParentsEquivalent(loci.structure, structure)) return false
|
||||
// TODO mark link only when both of the link elements are in a StructureElement.Loci
|
||||
for (const e of loci.elements) {
|
||||
OrderedSet.forEach(e.indices, v => {
|
||||
|
||||
@@ -121,7 +121,7 @@ function eachLink(loci: Loci, structureGroup: StructureGroup, apply: (interval:
|
||||
let changed = false
|
||||
if (Link.isLoci(loci)) {
|
||||
const { structure, group } = structureGroup
|
||||
if (!Structure.areEquivalent(loci.structure, structure)) return false
|
||||
if (!Structure.areParentsEquivalent(loci.structure, structure)) return false
|
||||
const unit = group.units[0]
|
||||
if (!Unit.isAtomic(unit)) return false
|
||||
const groupCount = unit.links.edgeCount * 2
|
||||
@@ -136,7 +136,7 @@ function eachLink(loci: Loci, structureGroup: StructureGroup, apply: (interval:
|
||||
}
|
||||
} else if (StructureElement.isLoci(loci)) {
|
||||
const { structure, group } = structureGroup
|
||||
if (!Structure.areEquivalent(loci.structure, structure)) return false
|
||||
if (!Structure.areParentsEquivalent(loci.structure, structure)) return false
|
||||
const unit = group.units[0]
|
||||
if (!Unit.isAtomic(unit)) return false
|
||||
const groupCount = unit.links.edgeCount * 2
|
||||
|
||||
@@ -52,7 +52,7 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
|
||||
const isNucleicType = isNucleic(v.moleculeType)
|
||||
const isSheet = SecondaryStructureType.is(v.secStrucType, SecondaryStructureType.Flag.Beta)
|
||||
const isHelix = SecondaryStructureType.is(v.secStrucType, SecondaryStructureType.Flag.Helix)
|
||||
const tension = isNucleicType ? 0.5 : 0.9
|
||||
const tension = isHelix ? 0.9 : 0.5
|
||||
const shift = isNucleicType ? 0.3 : 0.5
|
||||
|
||||
interpolateCurveSegment(state, v, tension, shift)
|
||||
|
||||
@@ -72,7 +72,7 @@ export function eachElement(loci: Loci, structureGroup: StructureGroup, apply: (
|
||||
let changed = false
|
||||
if (!StructureElement.isLoci(loci)) return false
|
||||
const { structure, group } = structureGroup
|
||||
if (!Structure.areEquivalent(loci.structure, structure)) return false
|
||||
if (!Structure.areParentsEquivalent(loci.structure, structure)) return false
|
||||
const elementCount = group.elements.length
|
||||
for (const e of loci.elements) {
|
||||
const unitIdx = group.unitIndexMap.get(e.unit.id)
|
||||
|
||||
@@ -45,7 +45,7 @@ export function eachNucleotideElement(loci: Loci, structureGroup: StructureGroup
|
||||
let changed = false
|
||||
if (!StructureElement.isLoci(loci)) return false
|
||||
const { structure, group } = structureGroup
|
||||
if (!Structure.areEquivalent(loci.structure, structure)) return false
|
||||
if (!Structure.areParentsEquivalent(loci.structure, structure)) return false
|
||||
const unit = group.units[0]
|
||||
if (!Unit.isAtomic(unit)) return false
|
||||
const { nucleotideElements, model, elements } = unit
|
||||
|
||||
@@ -95,7 +95,7 @@ export function eachPolymerElement(loci: Loci, structureGroup: StructureGroup, a
|
||||
let changed = false
|
||||
if (!StructureElement.isLoci(loci)) return false
|
||||
const { structure, group } = structureGroup
|
||||
if (!Structure.areEquivalent(loci.structure, structure)) return false
|
||||
if (!Structure.areParentsEquivalent(loci.structure, structure)) return false
|
||||
const { polymerElements, model, elements } = group.units[0]
|
||||
const { index, offsets } = model.atomicHierarchy.residueAtomSegments
|
||||
const { traceElementIndex } = model.atomicHierarchy.derived.residue
|
||||
@@ -156,7 +156,7 @@ export function eachPolymerGapElement(loci: Loci, structureGroup: StructureGroup
|
||||
let changed = false
|
||||
if (Link.isLoci(loci)) {
|
||||
const { structure, group } = structureGroup
|
||||
if (!Structure.areEquivalent(loci.structure, structure)) return false
|
||||
if (!Structure.areParentsEquivalent(loci.structure, structure)) return false
|
||||
const groupCount = group.units[0].gapElements.length
|
||||
for (const b of loci.links) {
|
||||
const unitIdx = group.unitIndexMap.get(b.aUnit.id)
|
||||
@@ -170,7 +170,7 @@ export function eachPolymerGapElement(loci: Loci, structureGroup: StructureGroup
|
||||
}
|
||||
} else if (StructureElement.isLoci(loci)) {
|
||||
const { structure, group } = structureGroup
|
||||
if (!Structure.areEquivalent(loci.structure, structure)) return false
|
||||
if (!Structure.areParentsEquivalent(loci.structure, structure)) return false
|
||||
const groupCount = group.units[0].gapElements.length
|
||||
for (const e of loci.elements) {
|
||||
const unitIdx = group.unitIndexMap.get(e.unit.id)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -19,6 +19,7 @@ export interface CurveSegmentState {
|
||||
}
|
||||
|
||||
export interface CurveSegmentControls {
|
||||
secStrucFirst: boolean, secStrucLast: boolean
|
||||
p0: Vec3, p1: Vec3, p2: Vec3, p3: Vec3, p4: Vec3,
|
||||
d12: Vec3, d23: Vec3
|
||||
}
|
||||
@@ -48,20 +49,25 @@ const curvePoint = Vec3.zero()
|
||||
|
||||
export function interpolatePointsAndTangents(state: CurveSegmentState, controls: CurveSegmentControls, tension: number, shift: number) {
|
||||
const { curvePoints, tangentVectors, linearSegments } = state
|
||||
const { p0, p1, p2, p3, p4 } = controls
|
||||
const { p0, p1, p2, p3, p4, secStrucFirst, secStrucLast } = controls
|
||||
|
||||
const shift1 = 1 - shift
|
||||
|
||||
const tensionBeg = secStrucFirst ? 0.5 : tension
|
||||
const tensionEnd = secStrucLast ? 0.5 : tension
|
||||
|
||||
for (let j = 0; j <= linearSegments; ++j) {
|
||||
const t = j * 1.0 / linearSegments;
|
||||
if (t < shift1) {
|
||||
Vec3.spline(curvePoint, p0, p1, p2, p3, t + shift, tension)
|
||||
Vec3.spline(tanA, p0, p1, p2, p3, t + shift + 0.01, tension)
|
||||
Vec3.spline(tanB, p0, p1, p2, p3, t + shift - 0.01, tension)
|
||||
const te = lerp(tensionBeg, tension, t)
|
||||
Vec3.spline(curvePoint, p0, p1, p2, p3, t + shift, te)
|
||||
Vec3.spline(tanA, p0, p1, p2, p3, t + shift + 0.01, tensionBeg)
|
||||
Vec3.spline(tanB, p0, p1, p2, p3, t + shift - 0.01, tensionBeg)
|
||||
} else {
|
||||
Vec3.spline(curvePoint, p1, p2, p3, p4, t - shift1, tension)
|
||||
Vec3.spline(tanA, p1, p2, p3, p4, t - shift1 + 0.01, tension)
|
||||
Vec3.spline(tanB, p1, p2, p3, p4, t - shift1 - 0.01, tension)
|
||||
const te = lerp(tension, tensionEnd, t)
|
||||
Vec3.spline(curvePoint, p1, p2, p3, p4, t - shift1, te)
|
||||
Vec3.spline(tanA, p1, p2, p3, p4, t - shift1 + 0.01, te)
|
||||
Vec3.spline(tanB, p1, p2, p3, p4, t - shift1 - 0.01, te)
|
||||
}
|
||||
Vec3.toArray(curvePoint, curvePoints, j * 3)
|
||||
Vec3.normalize(tangentVec, Vec3.sub(tangentVec, tanA, tanB))
|
||||
@@ -69,17 +75,15 @@ export function interpolatePointsAndTangents(state: CurveSegmentState, controls:
|
||||
}
|
||||
}
|
||||
|
||||
const tmpNormal = Vec3.zero()
|
||||
const tangentVec = Vec3.zero()
|
||||
const normalVec = Vec3.zero()
|
||||
const binormalVec = Vec3.zero()
|
||||
const prevNormal = Vec3.zero()
|
||||
const firstControlPoint = Vec3.zero()
|
||||
const lastControlPoint = Vec3.zero()
|
||||
const firstTangentVec = Vec3.zero()
|
||||
const lastTangentVec = Vec3.zero()
|
||||
const firstNormalVec = Vec3.zero()
|
||||
const lastNormalVec = Vec3.zero()
|
||||
const tmpNormal = Vec3()
|
||||
const tangentVec = Vec3()
|
||||
const normalVec = Vec3()
|
||||
const binormalVec = Vec3()
|
||||
const prevNormal = Vec3()
|
||||
const firstTangentVec = Vec3()
|
||||
const lastTangentVec = Vec3()
|
||||
const firstNormalVec = Vec3()
|
||||
const lastNormalVec = Vec3()
|
||||
|
||||
/**
|
||||
* Populate normalVectors by interpolating from firstDirection to lastDirection with
|
||||
@@ -91,13 +95,11 @@ export function interpolateNormals(state: CurveSegmentState, controls: CurveSegm
|
||||
|
||||
const n = curvePoints.length / 3
|
||||
|
||||
Vec3.fromArray(firstControlPoint, curvePoints, 0)
|
||||
Vec3.fromArray(lastControlPoint, curvePoints, (n - 1) * 3)
|
||||
Vec3.fromArray(firstTangentVec, tangentVectors, 0)
|
||||
Vec3.fromArray(lastTangentVec, tangentVectors, (n - 1) * 3)
|
||||
Vec3.fromArray(lastTangentVec, tangentVectors, (n - 1) * 3)
|
||||
|
||||
Vec3.orthogonalize(firstNormalVec, firstTangentVec, Vec3.sub(tmpNormal, firstControlPoint, firstDirection))
|
||||
Vec3.orthogonalize(lastNormalVec, lastTangentVec, Vec3.sub(tmpNormal, lastControlPoint, lastDirection))
|
||||
Vec3.orthogonalize(firstNormalVec, firstTangentVec, firstDirection)
|
||||
Vec3.orthogonalize(lastNormalVec, lastTangentVec, lastDirection)
|
||||
|
||||
if (Vec3.dot(firstNormalVec, lastNormalVec) < 0) {
|
||||
Vec3.scale(lastNormalVec, lastNormalVec, -1)
|
||||
|
||||
@@ -57,13 +57,16 @@ function createPolymerTraceElement (unit: Unit): PolymerTraceElement {
|
||||
moleculeType: MoleculeType.unknown,
|
||||
coarseBackboneFirst: false, coarseBackboneLast: false,
|
||||
isCoarseBackbone: false,
|
||||
p0: Vec3.zero(), p1: Vec3.zero(), p2: Vec3.zero(), p3: Vec3.zero(), p4: Vec3.zero(),
|
||||
d12: Vec3.create(1, 0, 0), d23: Vec3.create(1, 0, 0),
|
||||
p0: Vec3(), p1: Vec3(), p2: Vec3(), p3: Vec3(), p4: Vec3(),
|
||||
d12: Vec3(), d23: Vec3()
|
||||
}
|
||||
}
|
||||
|
||||
const enum AtomicPolymerTraceIteratorState { nextPolymer, nextResidue }
|
||||
|
||||
const tmpVecA = Vec3()
|
||||
const tmpVecB = Vec3()
|
||||
|
||||
export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement> {
|
||||
private value: PolymerTraceElement
|
||||
private polymerIt: SortedRanges.Iterator<ElementIndex, ResidueIndex>
|
||||
@@ -83,22 +86,23 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
|
||||
private state: AtomicPolymerTraceIteratorState = AtomicPolymerTraceIteratorState.nextPolymer
|
||||
private residueAtomSegments: Segmentation<ElementIndex, ResidueIndex>
|
||||
private traceElementIndex: ArrayLike<ElementIndex>
|
||||
private directionElementIndex: ArrayLike<ElementIndex | -1>
|
||||
private directionFromElementIndex: ArrayLike<ElementIndex | -1>
|
||||
private directionToElementIndex: ArrayLike<ElementIndex | -1>
|
||||
private moleculeType: ArrayLike<MoleculeType>
|
||||
private atomicConformation: AtomicConformation
|
||||
|
||||
private p0 = Vec3.zero();
|
||||
private p1 = Vec3.zero();
|
||||
private p2 = Vec3.zero();
|
||||
private p3 = Vec3.zero();
|
||||
private p4 = Vec3.zero();
|
||||
private p5 = Vec3.zero();
|
||||
private p6 = Vec3.zero();
|
||||
private p0 = Vec3()
|
||||
private p1 = Vec3()
|
||||
private p2 = Vec3()
|
||||
private p3 = Vec3()
|
||||
private p4 = Vec3()
|
||||
private p5 = Vec3()
|
||||
private p6 = Vec3()
|
||||
|
||||
// private v01 = Vec3.zero();
|
||||
private v12 = Vec3.create(1, 0, 0);
|
||||
private v23 = Vec3.create(1, 0, 0);
|
||||
// private v34 = Vec3.zero();
|
||||
private d01 = Vec3()
|
||||
private d12 = Vec3()
|
||||
private d23 = Vec3()
|
||||
private d34 = Vec3()
|
||||
|
||||
hasNext: boolean = false;
|
||||
|
||||
@@ -148,6 +152,20 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
|
||||
}
|
||||
}
|
||||
|
||||
private setFromToVector(out: Vec3, residueIndex: ResidueIndex) {
|
||||
this.pos(tmpVecA, this.directionFromElementIndex[residueIndex])
|
||||
this.pos(tmpVecB, this.directionToElementIndex[residueIndex])
|
||||
Vec3.sub(out, tmpVecB, tmpVecA)
|
||||
}
|
||||
|
||||
private setDirection(out: Vec3, v1: Vec3, v2: Vec3, v3: Vec3) {
|
||||
Vec3.copy(tmpVecA, v1)
|
||||
Vec3.copy(tmpVecB, v3)
|
||||
if (Vec3.dot(v2, tmpVecA) < 0) Vec3.scale(tmpVecA, tmpVecA, -1)
|
||||
if (Vec3.dot(v2, tmpVecB) < 0) Vec3.scale(tmpVecB, tmpVecB, -1)
|
||||
Vec3.scale(out, Vec3.add(out, tmpVecA, Vec3.add(out, tmpVecB, Vec3.add(out, v2, v2))), 1/4)
|
||||
}
|
||||
|
||||
move() {
|
||||
const { residueIt, polymerIt, value } = this
|
||||
|
||||
@@ -161,7 +179,7 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
|
||||
this.currSecStrucType = SecStrucTypeNA
|
||||
this.nextSecStrucType = this.getSecStruc(this.residueSegmentMin)
|
||||
this.currCoarseBackbone = false
|
||||
this.nextCoarseBackbone = this.directionElementIndex[this.residueSegmentMin] === -1
|
||||
this.nextCoarseBackbone = this.directionFromElementIndex[this.residueSegmentMin] === -1 || this.directionToElementIndex[this.residueSegmentMin] === -1
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -175,7 +193,7 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
|
||||
|
||||
this.prevCoarseBackbone = this.currCoarseBackbone
|
||||
this.currCoarseBackbone = this.nextCoarseBackbone
|
||||
this.nextCoarseBackbone = residueIt.hasNext ? this.directionElementIndex[residueIndex + 1] === -1 : false
|
||||
this.nextCoarseBackbone = residueIt.hasNext ? (this.directionFromElementIndex[residueIndex + 1] === -1 || this.directionToElementIndex[residueIndex + 1] === -1) : false
|
||||
|
||||
value.secStrucType = this.currSecStrucType
|
||||
value.secStrucFirst = this.prevSecStrucType !== this.currSecStrucType
|
||||
@@ -205,7 +223,9 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
|
||||
this.pos(this.p4, this.traceElementIndex[residueIndexNext1])
|
||||
this.pos(this.p5, this.traceElementIndex[residueIndexNext2])
|
||||
|
||||
this.pos(this.v12, this.directionElementIndex[residueIndexPrev1])
|
||||
this.setFromToVector(this.d01, residueIndexPrev1)
|
||||
this.setFromToVector(this.d12, residueIndex)
|
||||
this.setFromToVector(this.d23, residueIndexNext1)
|
||||
} else {
|
||||
value.centerPrev.element = value.center.element
|
||||
value.center.element = value.centerNext.element
|
||||
@@ -217,12 +237,14 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
|
||||
Vec3.copy(this.p4, this.p5)
|
||||
Vec3.copy(this.p5, this.p6)
|
||||
|
||||
Vec3.copy(this.v12, this.v23)
|
||||
Vec3.copy(this.d01, this.d12)
|
||||
Vec3.copy(this.d12, this.d23)
|
||||
Vec3.copy(this.d23, this.d34)
|
||||
}
|
||||
value.centerNext.element = this.traceElementIndex[residueIndexNext1]
|
||||
this.pos(this.p6, this.traceElementIndex[residueIndexNext3])
|
||||
this.pos(this.v23, this.directionElementIndex[residueIndex])
|
||||
value.isCoarseBackbone = this.directionElementIndex[residueIndex] === -1
|
||||
this.pos(this.p6, this.traceElementIndex[residueIndexNext3])
|
||||
this.setFromToVector(this.d34, residueIndexNext2)
|
||||
value.isCoarseBackbone = this.directionFromElementIndex[residueIndex] === -1 || this.directionToElementIndex[residueIndex] === -1
|
||||
|
||||
this.setControlPoint(value.p0, this.p0, this.p1, this.p2, residueIndexPrev2)
|
||||
this.setControlPoint(value.p1, this.p1, this.p2, this.p3, residueIndexPrev1)
|
||||
@@ -230,8 +252,8 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
|
||||
this.setControlPoint(value.p3, this.p3, this.p4, this.p5, residueIndexNext1)
|
||||
this.setControlPoint(value.p4, this.p4, this.p5, this.p6, residueIndexNext2)
|
||||
|
||||
Vec3.copy(value.d12, this.v12)
|
||||
Vec3.copy(value.d23, this.v23)
|
||||
this.setDirection(value.d12, this.d01, this.d12, this.d23)
|
||||
this.setDirection(value.d23, this.d12, this.d23, this.d34)
|
||||
|
||||
if (!residueIt.hasNext) {
|
||||
this.state = AtomicPolymerTraceIteratorState.nextPolymer
|
||||
@@ -247,7 +269,8 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
|
||||
this.atomicConformation = unit.model.atomicConformation
|
||||
this.residueAtomSegments = unit.model.atomicHierarchy.residueAtomSegments
|
||||
this.traceElementIndex = unit.model.atomicHierarchy.derived.residue.traceElementIndex as ArrayLike<ElementIndex> // can assume it won't be -1 for polymer residues
|
||||
this.directionElementIndex = unit.model.atomicHierarchy.derived.residue.directionElementIndex
|
||||
this.directionFromElementIndex = unit.model.atomicHierarchy.derived.residue.directionFromElementIndex
|
||||
this.directionToElementIndex = unit.model.atomicHierarchy.derived.residue.directionToElementIndex
|
||||
this.moleculeType = unit.model.atomicHierarchy.derived.residue.moleculeType
|
||||
this.cyclicPolymerMap = unit.model.atomicHierarchy.cyclicPolymerMap
|
||||
this.polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements)
|
||||
|
||||
@@ -162,7 +162,7 @@ class State {
|
||||
return cell && cell.obj;
|
||||
}
|
||||
} finally {
|
||||
this.spine.setSurrent();
|
||||
this.spine.setCurrent();
|
||||
|
||||
if (updated) this.events.changed.next();
|
||||
this.events.isUpdating.next(false);
|
||||
@@ -607,7 +607,7 @@ async function updateNode(ctx: UpdateContext, currentRef: Ref): Promise<UpdateNo
|
||||
throw new Error(`No suitable parent found for '${currentRef}'`);
|
||||
}
|
||||
|
||||
ctx.spine.setSurrent(current);
|
||||
ctx.spine.setCurrent(current);
|
||||
|
||||
const parent = parentCell.obj!;
|
||||
current.sourceRef = parentCell.transform.ref;
|
||||
|
||||
@@ -19,7 +19,7 @@ interface StateTreeSpine {
|
||||
namespace StateTreeSpine {
|
||||
export class Impl implements StateTreeSpine {
|
||||
private current: StateObjectCell | undefined = void 0;
|
||||
setSurrent(cell?: StateObjectCell) {
|
||||
setCurrent(cell?: StateObjectCell) {
|
||||
this.current = cell;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export namespace ConsoleLogger {
|
||||
console.log(`[${tag}] ${msg}`);
|
||||
}
|
||||
|
||||
export function logId(guid: string | String, tag: string, msg: string) {
|
||||
export function logId(guid: string, tag: string, msg: string) {
|
||||
console.log(`[${guid}][${tag}] ${msg}`);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export namespace ConsoleLogger {
|
||||
console.error(`[Warn] (${ctx}) ${e}`);
|
||||
}
|
||||
|
||||
export function errorId(guid: string | String, e: any) {
|
||||
export function errorId(guid: string, e: any) {
|
||||
console.error(`[${guid}][Error] ${e}`);
|
||||
if (e.stack) console.error(e.stack);
|
||||
}
|
||||
|
||||
@@ -76,11 +76,11 @@ export namespace ParamDefinition {
|
||||
return setInfo<MultiSelect<E, T>>({ type: 'multi-select', defaultValue, options }, info)
|
||||
}
|
||||
|
||||
export interface Boolean extends Base<boolean> {
|
||||
export interface BooleanParam extends Base<boolean> {
|
||||
type: 'boolean'
|
||||
}
|
||||
export function Boolean(defaultValue: boolean, info?: Info): Boolean {
|
||||
return setInfo<Boolean>({ type: 'boolean', defaultValue }, info)
|
||||
export function Boolean(defaultValue: boolean, info?: Info): BooleanParam {
|
||||
return setInfo<BooleanParam>({ type: 'boolean', defaultValue }, info)
|
||||
}
|
||||
|
||||
export interface Text<T extends string = string> extends Base<T> {
|
||||
@@ -237,7 +237,7 @@ export namespace ParamDefinition {
|
||||
}
|
||||
|
||||
export type Any =
|
||||
| Value<any> | Select<any> | MultiSelect<any> | Boolean | Text | Color | Vec3 | Numeric | FileParam | Interval | LineGraph
|
||||
| Value<any> | Select<any> | MultiSelect<any> | BooleanParam | Text | Color | Vec3 | Numeric | FileParam | Interval | LineGraph
|
||||
| ColorScale<any> | Group<any> | Mapped<any> | Converted<any, any> | Conditioned<any, any, any> | ScriptExpression | ObjectList
|
||||
|
||||
export type Params = { [k: string]: Any }
|
||||
|
||||
21
src/servers/common/util.ts
Normal file
21
src/servers/common/util.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 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 { ConsoleLogger } from '../../mol-util/console-logger';
|
||||
|
||||
export function getParam<T>(params: any, ...path: string[]): T | undefined {
|
||||
try {
|
||||
let current = params;
|
||||
for (const p of path) {
|
||||
if (typeof current === 'undefined') return;
|
||||
current = current[p];
|
||||
}
|
||||
return current;
|
||||
} catch (e) {
|
||||
ConsoleLogger.error('Config', `Unable to retrieve property ${path.join('.')} from ${JSON.stringify(params)}`);
|
||||
}
|
||||
}
|
||||
@@ -54,8 +54,9 @@ const config = {
|
||||
*/
|
||||
customProperties: <ModelPropertyProviderConfig | string>{
|
||||
sources: [
|
||||
'./properties/pdbe',
|
||||
'./properties/rcsb'
|
||||
'pdbe',
|
||||
'rcsb',
|
||||
'wwpdb'
|
||||
],
|
||||
params: {
|
||||
PDBe: {
|
||||
@@ -68,6 +69,14 @@ const config = {
|
||||
File: {
|
||||
residuewise_outlier_summary: 'e:/test/mol-star/model/props/'
|
||||
}
|
||||
},
|
||||
RCSB: {
|
||||
API: {
|
||||
assembly_symmetry: 'https://rest-staging.rcsb.org/graphql'
|
||||
}
|
||||
},
|
||||
wwPDB: {
|
||||
chemCompBondTablePath: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -10,9 +10,27 @@ import * as argparse from 'argparse'
|
||||
import { runMaster, PreprocessEntry } from './parallel';
|
||||
import { ModelPropertyProviderConfig } from '../property-provider';
|
||||
|
||||
function description() {
|
||||
const exampleCfg = {
|
||||
numProcesses: 1,
|
||||
customProperties: {
|
||||
sources: [
|
||||
'wwpdb'
|
||||
],
|
||||
params: {
|
||||
wwPDB: {
|
||||
chemCompBondTablePath: './build/data/ccb.bcif'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return `Preprocess CIF files to include custom properties and convert them to BinaryCIF format.\n\nExample cfg.json: ${JSON.stringify(exampleCfg, null, 2)}`
|
||||
}
|
||||
|
||||
const cmdParser = new argparse.ArgumentParser({
|
||||
addHelp: true,
|
||||
description: 'Preprocess CIF files to include custom properties and convert them to BinaryCIF format.'
|
||||
description: description()
|
||||
});
|
||||
cmdParser.addArgument(['--input', '-i'], { help: 'Input filename', required: false });
|
||||
cmdParser.addArgument(['--outCIF', '-oc'], { help: 'Output CIF filename', required: false });
|
||||
|
||||
@@ -21,7 +21,6 @@ export function preprocessFile(filename: string, propertyProvider?: ModelPropert
|
||||
: convert(filename, outputCif, outputBcif);
|
||||
}
|
||||
|
||||
|
||||
async function preprocess(filename: string, propertyProvider?: ModelPropertiesProvider, outputCif?: string, outputBcif?: string) {
|
||||
const input = await readStructureWrapper('entry', '_local_', filename, propertyProvider);
|
||||
const categories = await classifyCif(input.cifFrame);
|
||||
|
||||
@@ -14,6 +14,7 @@ import { PDBePreferredAssembly } from '../../../../mol-model-props/pdbe/preferre
|
||||
import { PDBeStructRefDomain } from '../../../../mol-model-props/pdbe/struct-ref-domain';
|
||||
import { AttachModelProperty } from '../../property-provider';
|
||||
import { ConsoleLogger } from '../../../../mol-util/console-logger';
|
||||
import { getParam } from '../../../common/util';
|
||||
|
||||
export const PDBe_structureQualityReport: AttachModelProperty = ({ model, params, cache }) => {
|
||||
const PDBe_apiSourceJson = useFileSource(params)
|
||||
@@ -68,20 +69,6 @@ function useFileSource(params: any) {
|
||||
return !!getParam<boolean>(params, 'PDBe', 'UseFileSource')
|
||||
}
|
||||
|
||||
function getParam<T>(params: any, ...path: string[]): T | undefined {
|
||||
try {
|
||||
let current = params;
|
||||
for (const p of path) {
|
||||
if (typeof current === 'undefined') return;
|
||||
current = current[p];
|
||||
}
|
||||
return current;
|
||||
} catch (e) {
|
||||
ConsoleLogger.error('Config', `Unable to retrieve property ${path.join('.')} from ${JSON.stringify(params)}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function apiQueryProvider(urlPrefix: string, cache: any) {
|
||||
const cacheKey = UUID.create22();
|
||||
return async (model: Model) => {
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { AssemblySymmetry } from '../../../../mol-model-props/rcsb/assembly-symmetry';
|
||||
import { AttachModelProperty } from '../../property-provider';
|
||||
import { ajaxGet } from '../../../../mol-util/data-source';
|
||||
import { GraphQLClient } from '../../../../mol-util/graphql-client';
|
||||
import { getParam } from '../../../common/util';
|
||||
|
||||
export const RCSB_assemblySymmetry: AttachModelProperty = ({ model }) => {
|
||||
return AssemblySymmetry.attachFromCifOrAPI(model)
|
||||
export const RCSB_assemblySymmetry: AttachModelProperty = ({ model, params }) => {
|
||||
const url = getApiUrl(params, 'assembly_symmetry', `https:${AssemblySymmetry.GraphQLEndpointURL}`)
|
||||
const client = new GraphQLClient(url, ajaxGet)
|
||||
return AssemblySymmetry.attachFromCifOrAPI(model, client)
|
||||
}
|
||||
|
||||
function getApiUrl(params: any, name: string, fallback: string) {
|
||||
const path = getParam<string>(params, 'RCSB', 'API', name);
|
||||
if (!path) return fallback;
|
||||
return path;
|
||||
}
|
||||
44
src/servers/model/properties/providers/wwpdb.ts
Normal file
44
src/servers/model/properties/providers/wwpdb.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as fs from 'fs'
|
||||
import * as util from 'util'
|
||||
import { AttachModelProperty } from '../../property-provider';
|
||||
import { ChemCompBond } from '../../../../mol-model-props/wwpdb/chem-comp-bond';
|
||||
import { Table } from '../../../../mol-data/db';
|
||||
import { CIF } from '../../../../mol-io/reader/cif';
|
||||
import { getParam } from '../../../common/util';
|
||||
|
||||
require('util.promisify').shim()
|
||||
const readFile = util.promisify(fs.readFile)
|
||||
|
||||
export const wwPDB_chemCompBond: AttachModelProperty = ({ model, params }) => {
|
||||
const wwPDB_apiSourceTable = getChemCompBondTableProvider(getTablePath(params))
|
||||
return ChemCompBond.attachFromCifOrTable(model, { wwPDB_apiSourceTable });
|
||||
}
|
||||
|
||||
async function read(path: string) {
|
||||
return path.endsWith('.bcif') ? new Uint8Array(await readFile(path)) : readFile(path, 'utf8');
|
||||
}
|
||||
|
||||
function getChemCompBondTableProvider(path: string): () => Promise<Table<ChemCompBond.Schema['chem_comp_bond']>> {
|
||||
let chemCompBondTable: Table<ChemCompBond.Schema['chem_comp_bond']>
|
||||
return async function() {
|
||||
if (chemCompBondTable === undefined) {
|
||||
const parsed = await CIF.parse(await read(path)).run()
|
||||
if (parsed.isError) throw new Error(parsed.toString())
|
||||
const table = CIF.toDatabase(ChemCompBond.Schema, parsed.result.blocks[0])
|
||||
chemCompBondTable = table.chem_comp_bond
|
||||
}
|
||||
return chemCompBondTable
|
||||
}
|
||||
}
|
||||
|
||||
function getTablePath(params: any) {
|
||||
const path = getParam<string>(params, 'wwPDB', 'chemCompBondTablePath');
|
||||
if (!path) throw new Error(`wwPDB 'chemCompBondTablePath' not set!`);
|
||||
return path;
|
||||
}
|
||||
16
src/servers/model/properties/wwpdb.ts
Normal file
16
src/servers/model/properties/wwpdb.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { AttachModelProperties } from '../property-provider';
|
||||
import { wwPDB_chemCompBond } from './providers/wwpdb';
|
||||
|
||||
export const attachModelProperties: AttachModelProperties = (args) => {
|
||||
// return a list of promises that start attaching the props in parallel
|
||||
// (if there are downloads etc.)
|
||||
return [
|
||||
wwPDB_chemCompBond(args)
|
||||
];
|
||||
}
|
||||
@@ -9,6 +9,17 @@ import { Model } from '../../mol-model/structure';
|
||||
import Config from './config';
|
||||
import { ConsoleLogger } from '../../mol-util/console-logger';
|
||||
|
||||
// TODO enable dynamic imports again
|
||||
import * as pdbeProps from './properties/pdbe'
|
||||
import * as rcsbProps from './properties/rcsb'
|
||||
import * as wwpdbProps from './properties/wwpdb'
|
||||
|
||||
const attachModelProperties: { [k: string]: AttachModelProperties } = {
|
||||
pdbe: pdbeProps.attachModelProperties,
|
||||
rcsb: rcsbProps.attachModelProperties,
|
||||
wwpdb: wwpdbProps.attachModelProperties
|
||||
}
|
||||
|
||||
export interface ModelPropertyProviderConfig {
|
||||
sources: string[],
|
||||
params?: { [name: string]: any }
|
||||
@@ -39,7 +50,11 @@ export function createModelPropertiesProvider(configOrPath: ModelPropertyProvide
|
||||
|
||||
const ps: AttachModelProperties[] = [];
|
||||
for (const p of config.sources) {
|
||||
ps.push(require(p).attachModelProperties);
|
||||
if (p in attachModelProperties) {
|
||||
ps.push(attachModelProperties[p]);
|
||||
} else {
|
||||
ConsoleLogger.error('Config', `Could not find property provider '${p}', ignoring.`);
|
||||
}
|
||||
}
|
||||
|
||||
return (model, cache) => {
|
||||
|
||||
@@ -16,7 +16,7 @@ import { LimitsConfig, addLimitsArgs, setLimitsConfig } from './config';
|
||||
console.log(`VolumeServer Local ${VERSION}, (c) 2018-2019, Mol* contributors`);
|
||||
console.log();
|
||||
|
||||
function help() {
|
||||
function description() {
|
||||
const exampleJobs: LocalApi.JobEntry[] = [{
|
||||
source: {
|
||||
filename: `g:/test/mdb/xray-1tqn.mdb`,
|
||||
@@ -55,7 +55,7 @@ function help() {
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
addHelp: true,
|
||||
description: help()
|
||||
description: description()
|
||||
});
|
||||
addLimitsArgs(parser)
|
||||
parser.addArgument(['jobs'], {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
],
|
||||
"arrow-parens": false,
|
||||
"no-var-keyword": true,
|
||||
"no-construct": true,
|
||||
"ordered-imports": [false],
|
||||
"trailing-comma": [false],
|
||||
"class-name": false,
|
||||
@@ -17,6 +18,12 @@
|
||||
true,
|
||||
"spaces"
|
||||
],
|
||||
"ban-types": [
|
||||
true,
|
||||
["String", "Use primitive 'string' instead."],
|
||||
["Boolean", "Use primitive 'boolean' instead."],
|
||||
["Number", "Use primitive 'number' instead."]
|
||||
],
|
||||
"no-eval": true,
|
||||
"no-internal-module": true,
|
||||
"no-trailing-whitespace": true,
|
||||
|
||||
@@ -8,16 +8,18 @@ const sharedConfig = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
test: /\.(woff2?|ttf|otf|eot|svg|html)$/,
|
||||
include: [path.resolve(__dirname, 'lib/')],
|
||||
options: {
|
||||
name: '[name].[ext]'
|
||||
}
|
||||
use: [{
|
||||
loader: 'file-loader',
|
||||
options: { name: '[name].[ext]' }
|
||||
}]
|
||||
},
|
||||
{
|
||||
test: /\.(s*)css$/,
|
||||
use: [MiniCssExtractPlugin.loader, 'css-loader', 'resolve-url-loader', 'sass-loader']
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
'css-loader', 'resolve-url-loader', 'sass-loader'
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -73,6 +75,12 @@ function createNodeEntryPoint(name, dir, out) {
|
||||
target: 'node',
|
||||
entry: path.resolve(__dirname, `lib/${dir}/${name}.js`),
|
||||
output: { filename: `${name}.js`, path: path.resolve(__dirname, `build/${out}`) },
|
||||
externals: {
|
||||
argparse: 'require("argparse")',
|
||||
'node-fetch': 'require("node-fetch")',
|
||||
'util.promisify': 'require("util.promisify")',
|
||||
xhr2: 'require("xhr2")',
|
||||
},
|
||||
...sharedConfig
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user