Compare commits

..

1 Commits

Author SHA1 Message Date
Alexander Rose
1db0ada684 debugging 2022-08-20 16:50:39 -07:00
131 changed files with 1728 additions and 8205 deletions

View File

@@ -6,38 +6,6 @@ Note that since we don't clearly distinguish between a public and private interf
## [Unreleased]
## [v3.18.0] - 2022-09-17
- Integration of Dual depth peeling - OIT method
- Stereo camera improvements
- Fix param updates not applied
- Better param ranges and description
- Add timer.mark for left/right camera
## [v3.17.0] - 2022-09-11
- [Fix] Clone ``Canvas3DParams`` when creating a ``Canvas3D`` instance to prevent shared state between multiple instances
- Add ``includeResidueTest`` option to ``alignAndSuperposeWithSIFTSMapping``
- Add ``parentDisplay`` param for interactions representation.
- [Experimental] Add support for PyMOL, VMD, and Jmol atom expressions in selection scripts
- Support for ``failIfMajorPerformanceCaveat`` webgl attribute. Add ``PluginConfig.General.AllowMajorPerformanceCaveat`` and ``allow-major-performance-caveat`` Viewer GET param.
- Fix handling of PDB TER records (#549)
- Add support for getting multiple loci from a representation (``.getAllLoci()``)
- Add ``key`` property to intra- and inter-bonds for referencing source data
- Fix click event triggered after move
## [v3.16.0] - 2022-08-25
- Support ``globalColorParams`` and ``globalSymmetryParams`` in common representation params
- Support ``label`` parameter in ``Viewer.loadStructureFromUrl``
- Fix ``ViewportHelpContent`` Mouse Controls section
## [v3.15.0] - 2022-08-23
- Fix wboit in Safari >=15 (add missing depth renderbuffer to wboit pass)
- Add 'Around Camera' option to Volume streaming
- Avoid queuing more than one update in Volume streaming
## [v3.14.0] - 2022-08-20
- Expose inter-bonds compute params in structure

3039
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "3.18.0",
"version": "3.14.0",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -89,60 +89,57 @@
"Ludovic Autin <autin@scripps.edu>",
"Michal Malý <michal.maly@ibt.cas.cz>",
"Jiří Černý <jiri.cerny@ibt.cas.cz>",
"Panagiotis Tourlas <panagiot_tourlov@hotmail.com>",
"Adam Midlik <midlik@gmail.com>",
"Koya Sakuma <koya.sakuma.work@gmail.com>",
"Gianluca Tomasello <giagitom@gmail.com>"
"Panagiotis Tourlas <panagiot_tourlov@hotmail.com>"
],
"license": "MIT",
"devDependencies": {
"@graphql-codegen/add": "^3.2.1",
"@graphql-codegen/cli": "^2.12.0",
"@graphql-codegen/cli": "^2.11.6",
"@graphql-codegen/time": "^3.2.1",
"@graphql-codegen/typescript": "^2.7.3",
"@graphql-codegen/typescript-graphql-files-modules": "^2.2.1",
"@graphql-codegen/typescript-graphql-request": "^4.5.4",
"@graphql-codegen/typescript-graphql-request": "^4.5.3",
"@graphql-codegen/typescript-operations": "^2.5.3",
"@types/cors": "^2.8.12",
"@types/gl": "^4.1.1",
"@types/jest": "^29.0.3",
"@types/react": "^18.0.20",
"@types/jest": "^28.1.7",
"@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6",
"@typescript-eslint/eslint-plugin": "^5.37.0",
"@typescript-eslint/parser": "^5.37.0",
"@typescript-eslint/eslint-plugin": "^5.33.1",
"@typescript-eslint/parser": "^5.33.1",
"benchmark": "^2.1.4",
"concurrently": "^7.4.0",
"concurrently": "^7.3.0",
"cpx2": "^4.2.0",
"crypto-browserify": "^3.12.0",
"css-loader": "^6.7.1",
"eslint": "^8.23.1",
"eslint": "^8.22.0",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^6.2.0",
"fs-extra": "^10.1.0",
"graphql": "^16.6.0",
"http-server": "^14.1.1",
"jest": "^29.0.3",
"jest": "^28.1.3",
"mini-css-extract-plugin": "^2.6.1",
"path-browserify": "^1.0.1",
"raw-loader": "^4.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"sass": "^1.54.9",
"sass": "^1.54.5",
"sass-loader": "^13.0.2",
"simple-git": "^3.14.1",
"simple-git": "^3.12.0",
"stream-browserify": "^3.0.0",
"style-loader": "^3.3.1",
"ts-jest": "^29.0.1",
"typescript": "^4.8.3",
"ts-jest": "^28.0.8",
"typescript": "^4.7.4",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0"
},
"dependencies": {
"@types/argparse": "^2.0.10",
"@types/benchmark": "^2.1.2",
"@types/benchmark": "^2.1.1",
"@types/compression": "1.7.2",
"@types/express": "^4.17.14",
"@types/node": "^16.11.59",
"@types/express": "^4.17.13",
"@types/node": "^16.11.51",
"@types/node-fetch": "^2.6.2",
"@types/swagger-ui-dist": "3.30.1",
"argparse": "^2.0.1",

View File

@@ -166,7 +166,7 @@ class Viewer {
structures.push({ ref: structureProperties?.ref || structure.ref });
}
// remove current structures from hierarchy as they will be merged
// remove current structuresfrom hierarchy as they will be merged
// TODO only works with using loadStructuresFromUrlsAndMerge once
// need some more API metho to work with the hierarchy
this.plugin.managers.structure.hierarchy.updateCurrent(this.plugin.managers.structure.hierarchy.current.structures, 'remove');

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -202,14 +202,14 @@ const InteractionsPreset = StructureRepresentationPresetProvider({
const components = {
ligand: await presetStaticComponent(plugin, structureCell, 'ligand'),
surroundings: await plugin.builders.structure.tryCreateComponentFromSelection(structureCell, ligandSurroundings, `surroundings`),
interactions: await presetStaticComponent(plugin, structureCell, 'ligand'),
interactions: await plugin.builders.structure.tryCreateComponentFromSelection(structureCell, ligandPlusSurroundings, `interactions`)
};
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
const representations = {
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.3 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
ballAndStick: builder.buildRepresentation(update, components.surroundings, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.1, sizeAspectRatio: 1 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ball-and-stick' }),
interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams, material: CustomMaterial, includeParent: true, parentDisplay: 'between' }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }),
interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams, material: CustomMaterial }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }),
label: builder.buildRepresentation(update, components.surroundings, { type: 'label', typeParams: { ...typeParams, material: CustomMaterial, background: false, borderWidth: 0.1 }, color: 'uniform', colorParams: { value: Color(0x000000) } }, { tag: 'label' }),
};

View File

@@ -88,9 +88,7 @@ const DefaultViewerOptions = {
pickScale: PluginConfig.General.PickScale.defaultValue,
pickPadding: PluginConfig.General.PickPadding.defaultValue,
enableWboit: PluginConfig.General.EnableWboit.defaultValue,
enableDpoit: PluginConfig.General.EnableDpoit.defaultValue,
preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue,
allowMajorPerformanceCaveat: PluginConfig.General.AllowMajorPerformanceCaveat.defaultValue,
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
@@ -160,9 +158,7 @@ export class Viewer {
[PluginConfig.General.PickScale, o.pickScale],
[PluginConfig.General.PickPadding, o.pickPadding],
[PluginConfig.General.EnableWboit, o.enableWboit],
[PluginConfig.General.EnableDpoit, o.enableDpoit],
[PluginConfig.General.PreferWebGl1, o.preferWebgl1],
[PluginConfig.General.AllowMajorPerformanceCaveat, o.allowMajorPerformanceCaveat],
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
@@ -203,7 +199,7 @@ export class Viewer {
return PluginCommands.State.Snapshots.OpenUrl(this.plugin, { url, type });
}
loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions & { label?: string }) {
loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
@@ -212,7 +208,6 @@ export class Viewer {
url: Asset.Url(url),
format: format as any,
isBinary,
label: options?.label,
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
}
}
@@ -501,4 +496,4 @@ export const ViewerAutoPreset = StructureRepresentationPresetProvider({
return await PresetStructureRepresentations.auto.apply(ref, params, plugin);
}
}
});
});

View File

@@ -38,15 +38,6 @@
viewer.loadPdb('7bv2');
viewer.loadEmdb('EMD-30210', { detail: 6 });
// viewer.loadAllModelsOrAssemblyFromUrl('https://cs.litemol.org/5ire/full', 'mmcif', false, { representationParams: { theme: { globalName: 'operator-name' } } })
// viewer.loadStructureFromUrl('my url', 'pdb', false, {
// representationParams: {
// theme: {
// globalName: 'uniform',
// globalColorParams: { value: 0xff0000 }
// }
// },
// label: 'my structure'
// });
});
</script>
</body>

View File

@@ -60,9 +60,7 @@
var pickScale = getParam('pick-scale', '[^&]+').trim();
var pickPadding = getParam('pick-padding', '[^&]+').trim();
var disableWboit = getParam('disable-wboit', '[^&]+').trim() === '1';
var enableDpoit = getParam('enable-dpoit', '[^&]+').trim() === '1';
var preferWebgl1 = getParam('prefer-webgl1', '[^&]+').trim() === '1' || void 0;
var allowMajorPerformanceCaveat = getParam('allow-major-performance-caveat', '[^&]+').trim() === '1';
molstar.Viewer.create('app', {
layoutShowControls: !hideControls,
@@ -76,10 +74,8 @@
pixelScale: parseFloat(pixelScale) || 1,
pickScale: parseFloat(pickScale) || 0.25,
pickPadding: isNaN(parseFloat(pickPadding)) ? 1 : parseFloat(pickPadding),
enableWboit: (disableWboit || enableDpoit) ? false : void 0, // use default value if disable-wboit is not set
enableDpoit: enableDpoit ? true : void 0,
enableWboit: disableWboit ? false : void 0, // use default value if disable-wboit is not set
preferWebgl1: preferWebgl1,
allowMajorPerformanceCaveat: allowMajorPerformanceCaveat,
}).then(viewer => {
var snapshotId = getParam('snapshot-id', '[^&]+').trim();
if (snapshotId) viewer.setRemoteSnapshot(snapshotId);

View File

@@ -60,8 +60,6 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
}
componentDidMount() {
if (!this.plugin.canvas3d) return;
const merged = merge(
this.controls.behaviors.params,
this.plugin.canvas3d!.reprCount

View File

@@ -118,13 +118,11 @@ export class Mp4Controls extends PluginComponent {
}
private init() {
if (!this.plugin.canvas3d) return;
this.subscribe(this.plugin.managers.animation.events.updated.pipe(debounceTime(16)), () => {
this.sync();
});
this.subscribe(this.plugin.canvas3d.resized, () => this.syncInfo());
this.subscribe(this.plugin.canvas3d?.resized!, () => this.syncInfo());
this.subscribe(this.plugin.helpers.viewportScreenshot?.events.previewed!, () => this.syncInfo());
this.subscribe(this.plugin.behaviors.state.isBusy, b => this.updateCanApply(b));

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -13,8 +13,8 @@ import { Camera, ICamera } from '../camera';
import { Viewport } from './util';
export const StereoCameraParams = {
eyeSeparation: PD.Numeric(0.062, { min: 0.02, max: 0.1, step: 0.001 }, { description: 'Distance between left and right camera.' }),
focus: PD.Numeric(10, { min: 1, max: 20, step: 0.1 }, { description: 'Apparent object distance.' }),
eyeSeparation: PD.Numeric(0.064, { min: 0.01, max: 0.5, step: 0.001 }),
focus: PD.Numeric(10, { min: 1, max: 100, step: 0.01 }),
};
export const DefaultStereoCameraProps = PD.getDefaultValues(StereoCameraParams);
export type StereoCameraProps = PD.Values<typeof StereoCameraParams>

View File

@@ -3,7 +3,6 @@
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
* @author Gianluca Tomasello <giagitom@gmail.com>
*/
import { BehaviorSubject, Subscription } from 'rxjs';
@@ -40,10 +39,9 @@ import { Helper } from './helper/helper';
import { Passes } from './passes/passes';
import { shallowEqual } from '../mol-util';
import { MarkingParams } from './passes/marking';
import { GraphicsRenderVariantsBlended, GraphicsRenderVariantsWboit, GraphicsRenderVariantsDpoit } from '../mol-gl/webgl/render-item';
import { GraphicsRenderVariantsBlended, GraphicsRenderVariantsWboit } from '../mol-gl/webgl/render-item';
import { degToRad, radToDeg } from '../mol-math/misc';
import { AssetManager } from '../mol-util/assets';
import { deepClone } from '../mol-util/object';
export const Canvas3DParams = {
camera: PD.Group({
@@ -85,7 +83,6 @@ export const Canvas3DParams = {
cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }),
sceneRadiusFactor: PD.Numeric(1, { min: 1, max: 10, step: 0.1 }),
transparentBackground: PD.Boolean(false),
dpoitIterations: PD.Numeric(2, { min: 1, max: 10, step: 1 }),
multiSample: PD.Group(MultiSampleParams),
postprocessing: PD.Group(PostprocessingParams),
@@ -119,7 +116,6 @@ interface Canvas3DContext {
namespace Canvas3DContext {
export const DefaultAttribs = {
failIfMajorPerformanceCaveat: false,
/** true by default to avoid issues with Safari (Jan 2021) */
antialias: true,
/** true to support multiple Canvas3D objects with a single context */
@@ -129,19 +125,14 @@ namespace Canvas3DContext {
/** extra pixels to around target to check in case target is empty */
pickPadding: 1,
enableWboit: true,
enableDpoit: false,
preferWebGl1: false
};
export type Attribs = typeof DefaultAttribs
export function fromCanvas(canvas: HTMLCanvasElement, assetManager: AssetManager, attribs: Partial<Attribs> = {}): Canvas3DContext {
const a = { ...DefaultAttribs, ...attribs };
if (a.enableWboit && a.enableDpoit) throw new Error('Multiple transparency methods not allowed.');
const { failIfMajorPerformanceCaveat, antialias, preserveDrawingBuffer, pixelScale, preferWebGl1 } = a;
const { antialias, preserveDrawingBuffer, pixelScale, preferWebGl1 } = a;
const gl = getGLContext(canvas, {
failIfMajorPerformanceCaveat,
antialias,
preserveDrawingBuffer,
alpha: true, // the renderer requires an alpha channel
@@ -294,7 +285,7 @@ namespace Canvas3D {
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, page?: Vec2, position?: Vec3 }
export function create({ webgl, input, passes, attribs, assetManager }: Canvas3DContext, props: Partial<Canvas3DProps> = {}): Canvas3D {
const p: Canvas3DProps = { ...deepClone(DefaultCanvas3DParams), ...deepClone(props) };
const p: Canvas3DProps = { ...DefaultCanvas3DParams, ...props };
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>();
const reprUpdatedSubscriptions = new Map<Representation.Any, Subscription>();
@@ -311,7 +302,8 @@ namespace Canvas3D {
let width = 128;
let height = 128;
updateViewport();
const scene = Scene.create(webgl, passes.draw.dpoitEnabled ? GraphicsRenderVariantsDpoit : (passes.draw.wboitEnabled ? GraphicsRenderVariantsWboit : GraphicsRenderVariantsBlended));
const scene = Scene.create(webgl, passes.draw.wboitEnabled ? GraphicsRenderVariantsWboit : GraphicsRenderVariantsBlended);
function getSceneRadius() {
return scene.boundingSphere.radius * p.sceneRadiusFactor;
@@ -691,7 +683,6 @@ namespace Canvas3D {
cameraResetDurationMs: p.cameraResetDurationMs,
sceneRadiusFactor: p.sceneRadiusFactor,
transparentBackground: p.transparentBackground,
dpoitIterations: p.dpoitIterations,
viewport: p.viewport,
postprocessing: { ...p.postprocessing },
@@ -826,13 +817,9 @@ namespace Canvas3D {
if (props.camera?.helper) helper.camera.setProps(props.camera.helper);
if (props.camera?.manualReset !== undefined) p.camera.manualReset = props.camera.manualReset;
if (props.camera?.stereo !== undefined) {
Object.assign(p.camera.stereo, props.camera.stereo);
stereoCamera.setProps(p.camera.stereo.params);
}
if (props.camera?.stereo !== undefined) Object.assign(p.camera.stereo, props.camera.stereo);
if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs;
if (props.transparentBackground !== undefined) p.transparentBackground = props.transparentBackground;
if (props.dpoitIterations !== undefined) p.dpoitIterations = props.dpoitIterations;
if (props.viewport !== undefined) {
const doNotUpdate = p.viewport === props.viewport ||
(p.viewport.name === props.viewport.name && shallowEqual(p.viewport.params, props.viewport.params));
@@ -868,7 +855,7 @@ namespace Canvas3D {
}
},
getImagePass: (props: Partial<ImageProps> = {}) => {
return new ImagePass(webgl, assetManager, renderer, scene, camera, helper, passes.draw.wboitEnabled, passes.draw.dpoitEnabled, props);
return new ImagePass(webgl, assetManager, renderer, scene, camera, helper, passes.draw.wboitEnabled, props);
},
getRenderObjects(): GraphicsRenderObject[] {
const renderObjects: GraphicsRenderObject[] = [];
@@ -933,4 +920,4 @@ namespace Canvas3D {
Viewport.set(controls.viewport, x, y, width, height);
}
}
}
}

View File

@@ -1,309 +0,0 @@
/**
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Gianluca Tomasello <giagitom@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*
* Adapted from https://github.com/tsherif/webgl2examples, The MIT License, Copyright © 2017 Tarek Sherif, Shuai Shao
*/
import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable';
import { TextureSpec, UniformSpec, Values } from '../../mol-gl/renderable/schema';
import { ShaderCode } from '../../mol-gl/shader-code';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
import { Texture } from '../../mol-gl/webgl/texture';
import { ValueCell } from '../../mol-util';
import { quad_vert } from '../../mol-gl/shader/quad.vert';
import { evaluateDpoit_frag } from '../../mol-gl/shader/evaluate-dpoit.frag';
import { blendBackDpoit_frag } from '../../mol-gl/shader/blend-back-dpoit.frag';
import { Framebuffer } from '../../mol-gl/webgl/framebuffer';
import { Vec2 } from '../../mol-math/linear-algebra';
import { isDebugMode, isTimingMode } from '../../mol-util/debug';
import { isWebGL2 } from '../../mol-gl/webgl/compat';
const BlendBackDpoitSchema = {
...QuadSchema,
tDpoitBackColor: TextureSpec('texture', 'rgba', 'float', 'nearest'),
uTexSize: UniformSpec('v2'),
};
const BlendBackDpoitShaderCode = ShaderCode('blend-back-dpoit', quad_vert, blendBackDpoit_frag);
type BlendBackDpoitRenderable = ComputeRenderable<Values<typeof BlendBackDpoitSchema>>
function getBlendBackDpoitRenderable(ctx: WebGLContext, dopitBlendBackTexture: Texture): BlendBackDpoitRenderable {
const values: Values<typeof BlendBackDpoitSchema> = {
...QuadValues,
tDpoitBackColor: ValueCell.create(dopitBlendBackTexture),
uTexSize: ValueCell.create(Vec2.create(dopitBlendBackTexture.getWidth(), dopitBlendBackTexture.getHeight())),
};
const schema = { ...BlendBackDpoitSchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', BlendBackDpoitShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
const EvaluateDpoitSchema = {
...QuadSchema,
tDpoitFrontColor: TextureSpec('texture', 'rgba', 'float', 'nearest'),
uTexSize: UniformSpec('v2'),
};
const EvaluateDpoitShaderCode = ShaderCode('evaluate-dpoit', quad_vert, evaluateDpoit_frag);
type EvaluateDpoitRenderable = ComputeRenderable<Values<typeof EvaluateDpoitSchema>>
function getEvaluateDpoitRenderable(ctx: WebGLContext, dpoitFrontColorTexture: Texture): EvaluateDpoitRenderable {
const values: Values<typeof EvaluateDpoitSchema> = {
...QuadValues,
tDpoitFrontColor: ValueCell.create(dpoitFrontColorTexture),
uTexSize: ValueCell.create(Vec2.create(dpoitFrontColorTexture.getWidth(), dpoitFrontColorTexture.getHeight())),
};
const schema = { ...EvaluateDpoitSchema };
const renderItem = createComputeRenderItem(ctx, 'triangles', EvaluateDpoitShaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
export class DpoitPass {
private readonly DEPTH_CLEAR_VALUE = -99999.0; // NOTE same constant is set in shaders
private readonly MAX_DEPTH = 1.0;
private readonly MIN_DEPTH = 0.0;
private passCount = 0;
private writeId: number;
private readId: number;
private readonly blendBackRenderable: BlendBackDpoitRenderable;
private readonly renderable: EvaluateDpoitRenderable;
private readonly depthFramebuffers: Framebuffer[];
private readonly colorFramebuffers: Framebuffer[];
private readonly depthTextures: Texture[];
private readonly colorFrontTextures: Texture[];
private readonly colorBackTextures: Texture[];
private _supported = false;
get supported() {
return this._supported;
}
bind() {
const { state, gl, extensions: { blendMinMax } } = this.webgl;
// initialize
this.passCount = 0;
this.depthFramebuffers[0].bind();
state.clearColor(this.DEPTH_CLEAR_VALUE, this.DEPTH_CLEAR_VALUE, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
this.depthFramebuffers[1].bind();
state.clearColor(-this.MIN_DEPTH, this.MAX_DEPTH, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
this.colorFramebuffers[0].bind();
state.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
this.colorFramebuffers[1].bind();
state.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
this.depthFramebuffers[0].bind();
state.blendEquation(blendMinMax!.MAX);
state.depthMask(false);
return {
depth: this.depthTextures[1],
frontColor: this.colorFrontTextures[1],
backColor: this.colorBackTextures[1]
};
}
bindDualDepthPeeling() {
const { state, gl, extensions: { blendMinMax } } = this.webgl;
this.readId = this.passCount % 2;
this.writeId = 1 - this.readId; // ping-pong: 0 or 1
this.passCount += 1; // increment for next pass
this.depthFramebuffers[this.writeId].bind();
state.clearColor(this.DEPTH_CLEAR_VALUE, this.DEPTH_CLEAR_VALUE, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
this.colorFramebuffers[this.writeId].bind();
state.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
this.depthFramebuffers[this.writeId].bind();
state.blendEquation(blendMinMax!.MAX);
state.depthMask(false);
return {
depth: this.depthTextures[this.readId],
frontColor: this.colorFrontTextures[this.readId],
backColor: this.colorBackTextures[this.readId]
};
}
renderBlendBack() {
if (isTimingMode) this.webgl.timer.mark('DpoitPass.renderBlendBack');
const { state, gl } = this.webgl;
state.blendEquation(gl.FUNC_ADD);
state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
ValueCell.update(this.blendBackRenderable.values.tDpoitBackColor, this.colorBackTextures[this.writeId]);
this.blendBackRenderable.update();
this.blendBackRenderable.render();
if (isTimingMode) this.webgl.timer.markEnd('DpoitPass.renderBlendBack');
}
render() {
if (isTimingMode) this.webgl.timer.mark('DpoitPass.render');
const { state, gl } = this.webgl;
state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
ValueCell.update(this.renderable.values.tDpoitFrontColor, this.colorFrontTextures[this.writeId]);
this.renderable.update();
this.renderable.render();
if (isTimingMode) this.webgl.timer.markEnd('DpoitPass.render');
}
setSize(width: number, height: number) {
const [w, h] = this.renderable.values.uTexSize.ref.value;
if (width !== w || height !== h) {
for (let i = 0; i < 2; i++) {
this.depthTextures[i].define(width, height);
this.colorFrontTextures[i].define(width, height);
this.colorBackTextures[i].define(width, height);
}
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
ValueCell.update(this.blendBackRenderable.values.uTexSize, Vec2.set(this.blendBackRenderable.values.uTexSize.ref.value, width, height));
}
}
reset() {
if (this._supported) this._init();
}
private _init() {
const { extensions: { drawBuffers } } = this.webgl;
for (let i = 0; i < 2; i++) {
// depth
this.depthFramebuffers[i].bind();
drawBuffers!.drawBuffers([
drawBuffers!.COLOR_ATTACHMENT0,
drawBuffers!.COLOR_ATTACHMENT1,
drawBuffers!.COLOR_ATTACHMENT2
]);
this.colorFrontTextures[i].attachFramebuffer(this.depthFramebuffers[i], 'color0');
this.colorBackTextures[i].attachFramebuffer(this.depthFramebuffers[i], 'color1');
this.depthTextures[i].attachFramebuffer(this.depthFramebuffers[i], 'color2');
// color
this.colorFramebuffers[i].bind();
drawBuffers!.drawBuffers([
drawBuffers!.COLOR_ATTACHMENT0,
drawBuffers!.COLOR_ATTACHMENT1
]);
this.colorFrontTextures[i].attachFramebuffer(this.colorFramebuffers[i], 'color0');
this.colorBackTextures[i].attachFramebuffer(this.colorFramebuffers[i], 'color1');
}
}
static isSupported(webgl: WebGLContext) {
const { extensions: { drawBuffers, textureFloat, colorBufferFloat, blendMinMax } } = webgl;
if (!textureFloat || !colorBufferFloat || !drawBuffers || !blendMinMax) {
if (isDebugMode) {
const missing: string[] = [];
if (!textureFloat) missing.push('textureFloat');
if (!colorBufferFloat) missing.push('colorBufferFloat');
if (!drawBuffers) missing.push('drawBuffers');
if (!blendMinMax) missing.push('blendMinMax');
console.log(`Missing "${missing.join('", "')}" extensions required for "dpoit"`);
}
return false;
} else {
return true;
}
}
constructor(private webgl: WebGLContext, width: number, height: number) {
if (!DpoitPass.isSupported(webgl)) return;
const { resources, extensions: { colorBufferHalfFloat, textureHalfFloat } } = webgl;
// textures
if (isWebGL2(webgl.gl)) {
this.depthTextures = [
resources.texture('image-float32', 'rg', 'float', 'nearest'),
resources.texture('image-float32', 'rg', 'float', 'nearest')
];
this.colorFrontTextures = colorBufferHalfFloat && textureHalfFloat ? [
resources.texture('image-float16', 'rgba', 'fp16', 'nearest'),
resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
] : [
resources.texture('image-float32', 'rgba', 'float', 'nearest'),
resources.texture('image-float32', 'rgba', 'float', 'nearest')
];
this.colorBackTextures = colorBufferHalfFloat && textureHalfFloat ? [
resources.texture('image-float16', 'rgba', 'fp16', 'nearest'),
resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
] : [
resources.texture('image-float32', 'rgba', 'float', 'nearest'),
resources.texture('image-float32', 'rgba', 'float', 'nearest')
];
} else {
// in webgl1 drawbuffers must be in the same format for some reason
this.depthTextures = [
resources.texture('image-float32', 'rgba', 'float', 'nearest'),
resources.texture('image-float32', 'rgba', 'float', 'nearest')
];
this.colorFrontTextures = [
resources.texture('image-float32', 'rgba', 'float', 'nearest'),
resources.texture('image-float32', 'rgba', 'float', 'nearest')
];
this.colorBackTextures = [
resources.texture('image-float32', 'rgba', 'float', 'nearest'),
resources.texture('image-float32', 'rgba', 'float', 'nearest')
];
}
this.depthTextures[0].define(width, height);
this.depthTextures[1].define(width, height);
this.colorFrontTextures[0].define(width, height);
this.colorFrontTextures[1].define(width, height);
this.colorBackTextures[0].define(width, height);
this.colorBackTextures[1].define(width, height);
// framebuffers
this.depthFramebuffers = [resources.framebuffer(), resources.framebuffer()];
this.colorFramebuffers = [resources.framebuffer(), resources.framebuffer()];
// renderables
this.blendBackRenderable = getBlendBackDpoitRenderable(webgl, this.colorBackTextures[0]);
this.renderable = getEvaluateDpoitRenderable(webgl, this.colorFrontTextures[0]);
this._supported = true;
this._init();
}
}

View File

@@ -3,7 +3,6 @@
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
* @author Gianluca Tomasello <giagitom@gmail.com>
*/
import { WebGLContext } from '../../mol-gl/webgl/context';
@@ -18,7 +17,6 @@ import { Helper } from '../helper/helper';
import { StereoCamera } from '../camera/stereo';
import { WboitPass } from './wboit';
import { DpoitPass } from './dpoit';
import { AntialiasingPass, PostprocessingPass, PostprocessingProps } from './postprocessing';
import { MarkingPass, MarkingProps } from './marking';
import { CopyRenderable, createCopyRenderable } from '../../mol-gl/compute/util';
@@ -29,7 +27,6 @@ type Props = {
postprocessing: PostprocessingProps;
marking: MarkingProps;
transparentBackground: boolean;
dpoitIterations: number;
}
type RenderContext = {
@@ -55,7 +52,6 @@ export class DrawPass {
private copyFboPostprocessing: CopyRenderable;
private readonly wboit: WboitPass | undefined;
private readonly dpoit: DpoitPass | undefined;
private readonly marking: MarkingPass;
readonly postprocessing: PostprocessingPass;
private readonly antialiasing: AntialiasingPass;
@@ -64,12 +60,9 @@ export class DrawPass {
return !!this.wboit?.supported;
}
get dpoitEnabled() {
return !!this.dpoit?.supported;
}
constructor(private webgl: WebGLContext, assetManager: AssetManager, width: number, height: number, enableWboit: boolean, enableDpoit: boolean) {
constructor(private webgl: WebGLContext, assetManager: AssetManager, width: number, height: number, enableWboit: boolean) {
const { extensions, resources, isWebGL2 } = webgl;
this.drawTarget = createNullRenderTarget(webgl.gl);
this.colorTarget = webgl.createRenderTarget(width, height, true, 'uint8', 'linear');
this.packedDepth = !extensions.depthTexture;
@@ -85,7 +78,6 @@ export class DrawPass {
}
this.wboit = enableWboit ? new WboitPass(webgl, width, height) : undefined;
this.dpoit = enableDpoit ? new DpoitPass(webgl, width, height) : undefined;
this.marking = new MarkingPass(webgl, width, height);
this.postprocessing = new PostprocessingPass(webgl, assetManager, this);
this.antialiasing = new AntialiasingPass(webgl, this);
@@ -96,7 +88,6 @@ export class DrawPass {
reset() {
this.wboit?.reset();
this.dpoit?.reset();
}
setSize(width: number, height: number) {
@@ -120,68 +111,12 @@ export class DrawPass {
this.wboit.setSize(width, height);
}
if (this.dpoit?.supported) {
this.dpoit.setSize(width, height);
}
this.marking.setSize(width, height);
this.postprocessing.setSize(width, height);
this.antialiasing.setSize(width, height);
}
}
private _renderDpoit(renderer: Renderer, camera: ICamera, scene: Scene, iterations: number, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
if (!this.dpoit?.supported) throw new Error('expected dpoit to be supported');
this.depthTextureOpaque.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
renderer.clear(true);
// render opaque primitives
if (scene.hasOpaque) {
renderer.renderDpoitOpaque(scene.primitives, camera, null);
}
if (PostprocessingPass.isEnabled(postprocessingProps)) {
if (PostprocessingPass.isOutlineEnabled(postprocessingProps)) {
this.depthTargetTransparent.bind();
renderer.clearDepth(true);
if (scene.opacityAverage < 1) {
renderer.renderDepthTransparent(scene.primitives, camera, this.depthTextureOpaque);
}
}
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps);
}
// render transparent primitives
if (scene.opacityAverage < 1) {
const target = PostprocessingPass.isEnabled(postprocessingProps)
? this.postprocessing.target : this.colorTarget;
const dpoitTextures = this.dpoit.bind();
renderer.renderDpoitTransparent(scene.primitives, camera, this.depthTextureOpaque, dpoitTextures);
for (let i = 0; i < iterations; i++) {
if (isTimingMode) this.webgl.timer.mark('DpoitPass.layer');
const dpoitTextures = this.dpoit.bindDualDepthPeeling();
renderer.renderDpoitTransparent(scene.primitives, camera, this.depthTextureOpaque, dpoitTextures);
target.bind();
this.dpoit.renderBlendBack();
if (isTimingMode) this.webgl.timer.markEnd('DpoitPass.layer');
}
// evaluate dpoit
target.bind();
this.dpoit.render();
}
// render transparent volumes
if (scene.volumes.renderables.length > 0) {
renderer.renderDpoitVolume(scene.volumes, camera, this.depthTextureOpaque);
}
}
private _renderWboit(renderer: Renderer, camera: ICamera, scene: Scene, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
if (!this.wboit?.supported) throw new Error('expected wboit to be supported');
@@ -319,15 +254,13 @@ export class DrawPass {
if (this.wboitEnabled) {
this._renderWboit(renderer, camera, scene, transparentBackground, props.postprocessing);
} else if (this.dpoitEnabled) {
this._renderDpoit(renderer, camera, scene, props.dpoitIterations, transparentBackground, props.postprocessing);
} else {
this._renderBlended(renderer, camera, scene, !volumeRendering && !postprocessingEnabled && !antialiasingEnabled && toDrawingBuffer, transparentBackground, props.postprocessing);
}
const target = postprocessingEnabled
? this.postprocessing.target
: !toDrawingBuffer || volumeRendering || this.wboitEnabled || this.dpoitEnabled
: !toDrawingBuffer || volumeRendering || this.wboitEnabled
? this.colorTarget
: this.drawTarget;
@@ -370,7 +303,7 @@ export class DrawPass {
this.webgl.state.disable(this.webgl.gl.DEPTH_TEST);
if (postprocessingEnabled) {
this.copyFboPostprocessing.render();
} else if (volumeRendering || this.wboitEnabled || this.dpoitEnabled) {
} else if (volumeRendering || this.wboitEnabled) {
this.copyFboTarget.render();
}
}
@@ -390,12 +323,8 @@ export class DrawPass {
renderer.setPixelRatio(this.webgl.pixelRatio);
if (StereoCamera.is(camera)) {
if (isTimingMode) this.webgl.timer.mark('StereoCamera.left');
this._render(renderer, camera.left, scene, helper, toDrawingBuffer, transparentBackground, props);
if (isTimingMode) this.webgl.timer.markEnd('StereoCamera.left');
if (isTimingMode) this.webgl.timer.mark('StereoCamera.right');
this._render(renderer, camera.right, scene, helper, toDrawingBuffer, transparentBackground, props);
if (isTimingMode) this.webgl.timer.markEnd('StereoCamera.right');
} else {
this._render(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props);
}
@@ -410,4 +339,4 @@ export class DrawPass {
}
return this.colorTarget;
}
}
}

View File

@@ -22,7 +22,6 @@ import { AssetManager } from '../../mol-util/assets';
export const ImageParams = {
transparentBackground: PD.Boolean(false),
dpoitIterations: PD.Numeric(2, { min: 1, max: 10, step: 1 }),
multiSample: PD.Group(MultiSampleParams),
postprocessing: PD.Group(PostprocessingParams),
marking: PD.Group(MarkingParams),
@@ -49,10 +48,10 @@ export class ImagePass {
get width() { return this._width; }
get height() { return this._height; }
constructor(private webgl: WebGLContext, assetManager: AssetManager, private renderer: Renderer, private scene: Scene, private camera: Camera, helper: Helper, enableWboit: boolean, enableDpoit: boolean, props: Partial<ImageProps>) {
constructor(private webgl: WebGLContext, assetManager: AssetManager, private renderer: Renderer, private scene: Scene, private camera: Camera, helper: Helper, enableWboit: boolean, props: Partial<ImageProps>) {
this.props = { ...PD.getDefaultValues(ImageParams), ...props };
this.drawPass = new DrawPass(webgl, assetManager, 128, 128, enableWboit, enableDpoit);
this.drawPass = new DrawPass(webgl, assetManager, 128, 128, enableWboit);
this.multiSamplePass = new MultiSamplePass(webgl, this.drawPass);
this.multiSampleHelper = new MultiSampleHelper(this.multiSamplePass);

View File

@@ -61,7 +61,6 @@ type Props = {
postprocessing: PostprocessingProps
marking: MarkingProps
transparentBackground: boolean;
dpoitIterations: number;
}
type RenderContext = {

View File

@@ -15,9 +15,9 @@ export class Passes {
readonly pick: PickPass;
readonly multiSample: MultiSamplePass;
constructor(private webgl: WebGLContext, assetManager: AssetManager, attribs: Partial<{ pickScale: number, enableWboit: boolean, enableDpoit: boolean }> = {}) {
constructor(private webgl: WebGLContext, assetManager: AssetManager, attribs: Partial<{ pickScale: number, enableWboit: boolean }> = {}) {
const { gl } = webgl;
this.draw = new DrawPass(webgl, assetManager, gl.drawingBufferWidth, gl.drawingBufferHeight, attribs.enableWboit || false, attribs.enableDpoit || false);
this.draw = new DrawPass(webgl, assetManager, gl.drawingBufferWidth, gl.drawingBufferHeight, attribs.enableWboit || false);
this.pick = new PickPass(webgl, this.draw, attribs.pickScale || 0.25);
this.multiSample = new MultiSamplePass(webgl, this.draw);
}

View File

@@ -18,8 +18,6 @@ import { evaluateWboit_frag } from '../../mol-gl/shader/evaluate-wboit.frag';
import { Framebuffer } from '../../mol-gl/webgl/framebuffer';
import { Vec2 } from '../../mol-math/linear-algebra';
import { isDebugMode, isTimingMode } from '../../mol-util/debug';
import { isWebGL2 } from '../../mol-gl/webgl/compat';
import { Renderbuffer } from '../../mol-gl/webgl/renderbuffer';
const EvaluateWboitSchema = {
...QuadSchema,
@@ -52,7 +50,6 @@ export class WboitPass {
private readonly framebuffer: Framebuffer;
private readonly textureA: Texture;
private readonly textureB: Texture;
private readonly depthRenderbuffer: Renderbuffer;
private _supported = false;
get supported() {
@@ -90,7 +87,6 @@ export class WboitPass {
if (width !== w || height !== h) {
this.textureA.define(width, height);
this.textureB.define(width, height);
this.depthRenderbuffer.setSize(width, height);
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
}
}
@@ -110,8 +106,6 @@ export class WboitPass {
this.textureA.attachFramebuffer(this.framebuffer, 'color0');
this.textureB.attachFramebuffer(this.framebuffer, 'color1');
this.depthRenderbuffer.attachFramebuffer(this.framebuffer);
}
static isSupported(webgl: WebGLContext) {
@@ -134,7 +128,7 @@ export class WboitPass {
constructor(private webgl: WebGLContext, width: number, height: number) {
if (!WboitPass.isSupported(webgl)) return;
const { resources, gl } = webgl;
const { resources } = webgl;
this.textureA = resources.texture('image-float32', 'rgba', 'float', 'nearest');
this.textureA.define(width, height);
@@ -142,10 +136,6 @@ export class WboitPass {
this.textureB = resources.texture('image-float32', 'rgba', 'float', 'nearest');
this.textureB.define(width, height);
this.depthRenderbuffer = isWebGL2(gl)
? resources.renderbuffer('depth32f', 'depth', width, height)
: resources.renderbuffer('depth16', 'depth', width, height);
this.renderable = getEvaluateWboitRenderable(webgl, this.textureA, this.textureB);
this.framebuffer = resources.framebuffer();

View File

@@ -53,17 +53,17 @@ describe('renderer', () => {
scene.commit();
expect(ctx.stats.resourceCounts.attribute).toBe(ctx.isWebGL2 ? 4 : 5);
expect(ctx.stats.resourceCounts.texture).toBe(9);
expect(ctx.stats.resourceCounts.vertexArray).toBe(ctx.extensions.vertexArrayObject ? 6 : 0);
expect(ctx.stats.resourceCounts.program).toBe(6);
expect(ctx.stats.resourceCounts.shader).toBe(12);
expect(ctx.stats.resourceCounts.vertexArray).toBe(ctx.extensions.vertexArrayObject ? 5 : 0);
expect(ctx.stats.resourceCounts.program).toBe(5);
expect(ctx.stats.resourceCounts.shader).toBe(10);
scene.remove(points);
scene.commit();
expect(ctx.stats.resourceCounts.attribute).toBe(0);
expect(ctx.stats.resourceCounts.texture).toBe(1);
expect(ctx.stats.resourceCounts.vertexArray).toBe(0);
expect(ctx.stats.resourceCounts.program).toBe(6);
expect(ctx.stats.resourceCounts.shader).toBe(12);
expect(ctx.stats.resourceCounts.program).toBe(5);
expect(ctx.stats.resourceCounts.shader).toBe(10);
ctx.resources.destroy();
expect(ctx.stats.resourceCounts.program).toBe(0);

View File

@@ -2,7 +2,6 @@
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Gianluca Tomasello <giagitom@gmail.com>
*/
import { ValueCell } from '../../mol-util';
@@ -168,11 +167,6 @@ export type GlobalUniformValues = Values<GlobalUniformSchema>
export const GlobalTextureSchema = {
tDepth: TextureSpec('texture', 'depth', 'ushort', 'nearest'),
// dpoit
tDpoitDepth: TextureSpec('texture', 'rg', 'float', 'nearest'),
tDpoitFrontColor: TextureSpec('texture', 'rgba', 'float', 'nearest'),
tDpoitBackColor: TextureSpec('texture', 'rgba', 'float', 'nearest')
} as const;
export type GlobalTextureSchema = typeof GlobalTextureSchema
export type GlobalTextureValues = Values<GlobalTextureSchema>
@@ -242,7 +236,7 @@ export const TransparencySchema = {
uTransparencyGridDim: UniformSpec('v3'),
uTransparencyGridTransform: UniformSpec('v4'),
tTransparencyGrid: TextureSpec('texture', 'alpha', 'ubyte', 'linear'),
dTransparencyType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance'])
dTransparencyType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance']),
} as const;
export type TransparencySchema = typeof TransparencySchema
export type TransparencyValues = Values<TransparencySchema>
@@ -333,4 +327,4 @@ export const BaseSchema = {
invariantBoundingSphere: ValueSpec('sphere'),
} as const;
export type BaseSchema = typeof BaseSchema
export type BaseValues = Values<BaseSchema>
export type BaseValues = Values<BaseSchema>

View File

@@ -2,7 +2,6 @@
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Gianluca Tomasello <giagitom@gmail.com>
*/
import { Viewport } from '../mol-canvas3d/camera/util';
@@ -71,9 +70,6 @@ interface Renderer {
renderBlendedVolume: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
renderWboitOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
renderWboitTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
renderDpoitOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
renderDpoitTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, dpoitTextures: { depth: Texture, frontColor: Texture, backColor: Texture }) => void
renderDpoitVolume: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
setProps: (props: Partial<RendererProps>) => void
setViewport: (x: number, y: number, width: number, height: number) => void
@@ -145,7 +141,7 @@ namespace Renderer {
const enum Flag {
None = 0,
BlendedFront = 1,
BlendedBack = 2,
BlendedBack = 2
}
const enum Mask {
@@ -272,7 +268,7 @@ namespace Renderer {
}
if (r.values.dGeometryType.ref.value === 'directVolume') {
if (variant !== 'colorDpoit' && variant !== 'colorWboit' && variant !== 'colorBlended') {
if (variant !== 'colorWboit' && variant !== 'colorBlended') {
return; // only color supported
}
@@ -606,71 +602,6 @@ namespace Renderer {
if (isTimingMode) ctx.timer.markEnd('Renderer.renderWboitTransparent');
};
const renderDpoitOpaque = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
if (isTimingMode) ctx.timer.mark('Renderer.renderDpoitOpaque');
state.disable(gl.BLEND);
state.enable(gl.DEPTH_TEST);
state.depthMask(true);
updateInternal(group, camera, depthTexture, Mask.Opaque, false);
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
// TODO: simplify, handle in renderable.state???
// uAlpha is updated in "render" so we need to recompute it here
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
if ((alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dPointStyle?.ref.value !== 'fuzzy' && !r.values.dXrayShaded?.ref.value) || r.values.dTransparentBackfaces?.ref.value === 'opaque') {
renderObject(r, 'colorDpoit', Flag.None);
}
}
if (isTimingMode) ctx.timer.markEnd('Renderer.renderDpoitOpaque');
};
const renderDpoitTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, dpoitTextures: { depth: Texture, frontColor: Texture, backColor: Texture }) => {
if (isTimingMode) ctx.timer.mark('Renderer.renderDpoitTransparent');
state.enable(gl.BLEND);
arrayMapUpsert(sharedTexturesList, 'tDpoitDepth', dpoitTextures.depth);
arrayMapUpsert(sharedTexturesList, 'tDpoitFrontColor', dpoitTextures.frontColor);
arrayMapUpsert(sharedTexturesList, 'tDpoitBackColor', dpoitTextures.backColor);
updateInternal(group, camera, depthTexture, Mask.Transparent, false);
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
// TODO: simplify, handle in renderable.state???
// uAlpha is updated in "render" so we need to recompute it here
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dPointStyle?.ref.value === 'fuzzy' || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) {
renderObject(r, 'colorDpoit', Flag.None);
}
}
if (isTimingMode) ctx.timer.markEnd('Renderer.renderDpoitTransparent');
};
const renderDpoitVolume = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
if (isTimingMode) ctx.timer.mark('Renderer.renderDpoitVolume');
state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
state.enable(gl.BLEND);
updateInternal(group, camera, depthTexture, Mask.Transparent, false);
const { renderables } = group;
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i];
if (r.values.dGeometryType.ref.value === 'directVolume') {
renderObject(r, 'colorDpoit', Flag.None);
}
}
if (isTimingMode) ctx.timer.markEnd('Renderer.renderDpoitVolume');
};
return {
clear: (toBackgroundColor: boolean, ignoreTransparentBackground?: boolean) => {
state.enable(gl.SCISSOR_TEST);
@@ -714,9 +645,6 @@ namespace Renderer {
renderBlendedVolume,
renderWboitOpaque,
renderWboitTransparent,
renderDpoitOpaque,
renderDpoitTransparent,
renderDpoitVolume,
setProps: (props: Partial<RendererProps>) => {
if (props.backgroundColor !== undefined && props.backgroundColor !== p.backgroundColor) {
@@ -834,4 +762,4 @@ namespace Renderer {
}
}
export { Renderer };
export { Renderer };

View File

@@ -45,8 +45,8 @@ function calculateBoundingSphere(renderables: GraphicsRenderable[], boundingSphe
}
function renderableSort(a: GraphicsRenderable, b: GraphicsRenderable) {
const drawProgramIdA = (a.getProgram('colorBlended') || a.getProgram('colorWboit') || a.getProgram('colorDpoit')).id;
const drawProgramIdB = (b.getProgram('colorBlended') || b.getProgram('colorWboit') || b.getProgram('colorDpoit')).id;
const drawProgramIdA = (a.getProgram('colorBlended') || a.getProgram('colorWboit')).id;
const drawProgramIdB = (b.getProgram('colorBlended') || a.getProgram('colorWboit')).id;
const materialIdA = a.materialId;
const materialIdB = b.materialId;

View File

@@ -67,7 +67,6 @@ import { texture3d_from_1d_trilinear } from './shader/chunks/texture3d-from-1d-t
import { texture3d_from_2d_linear } from './shader/chunks/texture3d-from-2d-linear.glsl';
import { texture3d_from_2d_nearest } from './shader/chunks/texture3d-from-2d-nearest.glsl';
import { wboit_write } from './shader/chunks/wboit-write.glsl';
import { dpoit_write } from './shader/chunks/dpoit-write.glsl';
const ShaderChunks: { [k: string]: string } = {
apply_fog,
@@ -100,8 +99,7 @@ const ShaderChunks: { [k: string]: string } = {
texture3d_from_1d_trilinear,
texture3d_from_2d_linear,
texture3d_from_2d_nearest,
wboit_write,
dpoit_write
wboit_write
};
const reInclude = /^(?!\/\/)\s*#include\s+(\S+)/gm;

View File

@@ -1,20 +0,0 @@
/**
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Gianluca Tomasello <giagitom@gmail.com>
*/
export const blendBackDpoit_frag = `
precision highp float;
uniform sampler2D tDpoitBackColor;
uniform vec2 uTexSize;
void main() {
vec2 coords = gl_FragCoord.xy / uTexSize;
gl_FragColor = texture2D(tDpoitBackColor, coords);
if (gl_FragColor.a == 0.0) {
discard;
}
}
`;

View File

@@ -12,19 +12,8 @@ if (!uTransparentBackground) {
gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor);
}
} else {
#if defined(dRenderVariant_colorDpoit)
if (gl_FragColor.a < 1.0) {
// transparent objects are blended with background color
gl_FragColor.a = fogAlpha;
} else {
// opaque objects need to be pre-multiplied alpha
gl_FragColor.rgb *= fogAlpha;
gl_FragColor.a = fogAlpha;
}
#else
// pre-multiplied alpha expected for transparent background
gl_FragColor.rgb *= fogAlpha;
gl_FragColor.a = fogAlpha;
#endif
// pre-multiplied alpha expected for transparent background
gl_FragColor.rgb *= fogAlpha;
gl_FragColor.a = fogAlpha;
}
`;

View File

@@ -86,7 +86,7 @@ export const assign_material_color = `
// apply per-group transparency
#if defined(dTransparency) && (defined(dRenderVariant_pick) || defined(dRenderVariant_color))
float ta = 1.0 - vTransparency;
if (vTransparency < 0.09) ta = 1.0; // hard cutoff looks better
if (vTransparency < 0.2) ta = 1.0; // hard cutoff looks better
#if defined(dRenderVariant_pick)
if (ta < uPickingAlphaThreshold)

View File

@@ -39,12 +39,6 @@ uniform int uMarkingType;
#endif
#endif
#if defined(dRenderVariant_colorDpoit)
#define MAX_DPOIT_DEPTH 99999.0 // NOTE constant also set in TypeScript
uniform sampler2D tDpoitDepth;
uniform sampler2D tDpoitFrontColor;
#endif
varying vec3 vModelPosition;
varying vec3 vViewPosition;

View File

@@ -44,9 +44,10 @@ varying vec3 vModelPosition;
varying vec3 vViewPosition;
#if defined(noNonInstancedActiveAttribs)
// int() is needed for some Safari versions
// see https://bugs.webkit.org/show_bug.cgi?id=244152
#define VertexID int(gl_VertexID)
#define VertexID gl_VertexID // for testing
// // int() is needed for some Safari versions
// // see https://bugs.webkit.org/show_bug.cgi?id=244152
// #define VertexID int(gl_VertexID)
#else
attribute float aVertex;
#define VertexID int(aVertex)

View File

@@ -1,7 +1,7 @@
export const common = `
// TODO find a better place for these convenience defines
#if defined(dRenderVariant_colorBlended) || defined(dRenderVariant_colorWboit) || defined(dRenderVariant_colorDpoit)
#if defined(dRenderVariant_colorBlended) || defined(dRenderVariant_colorWboit)
#define dRenderVariant_color
#endif

View File

@@ -1,70 +0,0 @@
/**
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Gianluca Tomasello <giagitom@gmail.com>
*/
export const dpoit_write = `
#if defined(dRenderVariant_colorDpoit)
if (uRenderMask == MaskOpaque) {
if (preFogAlpha < 1.0) {
discard;
}
} else if (uRenderMask == MaskTransparent) {
// the 'fragmentDepth > 0.99' check is to handle precision issues with packed depth
vec2 coords = gl_FragCoord.xy / uDrawingBufferSize;
if (preFogAlpha != 1.0 && (fragmentDepth < getDepth(coords) || fragmentDepth > 0.99)) {
#ifdef dTransparentBackfaces_off
if (interior) discard;
#endif
// adapted from https://github.com/tsherif/webgl2examples
// The MIT License, Copyright 2017 Tarek Sherif, Shuai Shao
vec2 lastDepth = texture2D(tDpoitDepth, coords).rg;
vec4 lastFrontColor = texture2D(tDpoitFrontColor, coords);
vec4 fragColor = gl_FragColor;
// depth value always increases
// so we can use MAX blend equation
gl_FragData[2].rg = vec2(-MAX_DPOIT_DEPTH);
// front color always increases
// so we can use MAX blend equation
gl_FragColor = lastFrontColor;
// back color is separately blend afterwards each pass
gl_FragData[1] = vec4(0.0);
float nearestDepth = - lastDepth.x;
float furthestDepth = lastDepth.y;
float alphaMultiplier = 1.0 - lastFrontColor.a;
if (fragmentDepth < nearestDepth || fragmentDepth > furthestDepth) {
// Skip this depth since it's been peeled.
return;
}
if (fragmentDepth > nearestDepth && fragmentDepth < furthestDepth) {
// This needs to be peeled.
// The ones remaining after MAX blended for
// all need-to-peel will be peeled next pass.
gl_FragData[2].rg = vec2(-fragmentDepth, fragmentDepth);
return;
}
// write to back and front color buffer
if (fragmentDepth == nearestDepth) {
gl_FragColor.rgb += fragColor.rgb * fragColor.a * alphaMultiplier;
gl_FragColor.a = 1.0 - alphaMultiplier * (1.0 - fragColor.a);
} else {
gl_FragData[1] += fragColor;
}
} else {
discard;
}
}
#endif
`;

View File

@@ -109,14 +109,14 @@ void main() {
vec3 vViewPosition = vModelPosition + intersection.x * rayDir;
vViewPosition = (uView * vec4(vViewPosition, 1.0)).xyz;
float fragmentDepth = calcDepth(vViewPosition);
if (fragmentDepth < 0.0) discard;
if (fragmentDepth > 1.0) discard;
gl_FragDepthEXT = fragmentDepth;
gl_FragDepthEXT = calcDepth(vViewPosition);
vec3 vModelPosition = (uInvView * vec4(vViewPosition, 1.0)).xyz;
if (gl_FragDepthEXT < 0.0) discard;
if (gl_FragDepthEXT > 1.0) discard;
float fragmentDepth = gl_FragDepthEXT;
#include assign_material_color
#if defined(dRenderVariant_pick)
@@ -142,7 +142,6 @@ void main() {
#include apply_marker_color
#include apply_fog
#include wboit_write
#include dpoit_write
#endif
}
`;
`;

View File

@@ -356,4 +356,4 @@ void main() {
float preFogAlpha = clamp(preFogAlphaBlended, 0.0, 1.0);
#include wboit_write
}
`;
`;

View File

@@ -1,17 +0,0 @@
/**
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Gianluca Tomasello <giagitom@gmail.com>
*/
export const evaluateDpoit_frag = `
precision highp float;
uniform sampler2D tDpoitFrontColor;
uniform vec2 uTexSize;
void main() {
vec2 coords = gl_FragCoord.xy / uTexSize;
gl_FragColor = texture2D(tDpoitFrontColor, coords);
}
`;

View File

@@ -159,7 +159,6 @@ void main() {
#include apply_marker_color
#include apply_fog
#include wboit_write
#include dpoit_write
#endif
}
`;
`;

View File

@@ -39,7 +39,6 @@ void main(){
#include apply_marker_color
#include apply_fog
#include wboit_write
#include dpoit_write
#endif
}
`;
`;

View File

@@ -62,7 +62,6 @@ void main() {
#include apply_marker_color
#include apply_fog
#include wboit_write
#include dpoit_write
#endif
}
`;
`;

View File

@@ -55,7 +55,6 @@ void main(){
#include apply_marker_color
#include apply_fog
#include wboit_write
#include dpoit_write
#endif
}
`;
`;

View File

@@ -70,17 +70,17 @@ void main(void){
}
vec3 vViewPosition = cameraPos;
float fragmentDepth = calcDepth(vViewPosition);
if (!flag && fragmentDepth >= 0.0) {
fragmentDepth = 0.0 + (0.0000001 / vRadius);
gl_FragDepthEXT = calcDepth(vViewPosition);
if (!flag && gl_FragDepthEXT >= 0.0) {
gl_FragDepthEXT = 0.0 + (0.0000001 / vRadius);
}
if (fragmentDepth < 0.0) discard;
if (fragmentDepth > 1.0) discard;
gl_FragDepthEXT = fragmentDepth;
vec3 vModelPosition = (uInvView * vec4(vViewPosition, 1.0)).xyz;
if (gl_FragDepthEXT < 0.0) discard;
if (gl_FragDepthEXT > 1.0) discard;
float fragmentDepth = gl_FragDepthEXT;
#include assign_material_color
#if defined(dRenderVariant_pick)
@@ -105,7 +105,6 @@ void main(void){
#include apply_marker_color
#include apply_fog
#include wboit_write
#include dpoit_write
#endif
}
`;
`;

View File

@@ -83,7 +83,6 @@ void main(){
#include apply_marker_color
#include apply_fog
#include wboit_write
#include dpoit_write
#endif
}
`;
`;

View File

@@ -2,7 +2,6 @@
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Gianluca Tomasello <giagitom@gmail.com>
*/
import { createAttributeBuffers, ElementsBuffer, AttributeKind } from './buffer';
@@ -50,12 +49,11 @@ export interface RenderItem<T extends string> {
//
const GraphicsRenderVariant = { colorBlended: '', colorWboit: '', colorDpoit: '', pick: '', depth: '', marking: '' };
const GraphicsRenderVariant = { colorBlended: '', colorWboit: '', pick: '', depth: '', marking: '' };
export type GraphicsRenderVariant = keyof typeof GraphicsRenderVariant
export const GraphicsRenderVariants = Object.keys(GraphicsRenderVariant) as GraphicsRenderVariant[];
export const GraphicsRenderVariantsBlended = GraphicsRenderVariants.filter(v => !['colorWboit', 'colorDpoit'].includes(v));
export const GraphicsRenderVariantsWboit = GraphicsRenderVariants.filter(v => !['colorBlended', 'colorDpoit'].includes(v));
export const GraphicsRenderVariantsDpoit = GraphicsRenderVariants.filter(v => !['colorWboit', 'colorBlended'].includes(v));
export const GraphicsRenderVariantsBlended = GraphicsRenderVariants.filter(v => v !== 'colorWboit');
export const GraphicsRenderVariantsWboit = GraphicsRenderVariants.filter(v => v !== 'colorBlended');
const ComputeRenderVariant = { compute: '' };
export type ComputeRenderVariant = keyof typeof ComputeRenderVariant
@@ -369,4 +367,4 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
}
}
};
}
}

View File

@@ -2,7 +2,6 @@
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Gianluca Tomasello <giagitom@gmail.com>
*/
import { WebGLContext } from './context';
@@ -32,7 +31,7 @@ export type TextureKindValue = {
export type TextureValueType = ValueOf<TextureKindValue>
export type TextureKind = keyof TextureKindValue
export type TextureType = 'ubyte' | 'ushort' | 'float' | 'fp16' | 'int'
export type TextureFormat = 'alpha' | 'rg' | 'rgb' | 'rgba' | 'depth'
export type TextureFormat = 'alpha' | 'rgb' | 'rgba' | 'depth'
/** Numbers are shortcuts for color attachment */
export type TextureAttachment = 'depth' | 'stencil' | 'color0' | 'color1' | 'color2' | 'color3' | 'color4' | 'color5' | 'color6' | 'color7' | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
export type TextureFilter = 'nearest' | 'linear'
@@ -64,10 +63,6 @@ export function getFormat(gl: GLRenderingContext, format: TextureFormat, type: T
case 'rgb':
if (isWebGL2(gl) && type === 'int') return gl.RGB_INTEGER;
return gl.RGB;
case 'rg':
if (isWebGL2(gl) && type === 'float') return gl.RG;
else if (isWebGL2(gl) && type === 'int') return gl.RG_INTEGER;
else throw new Error('texture format "rg" requires webgl2 and type "float" or int"');
case 'rgba':
if (isWebGL2(gl) && type === 'int') return gl.RGBA_INTEGER;
return gl.RGBA;
@@ -85,13 +80,6 @@ export function getInternalFormat(gl: GLRenderingContext, format: TextureFormat,
case 'fp16': return gl.R16F;
case 'int': return gl.R32I;
}
case 'rg':
switch (type) {
case 'ubyte': return gl.RG;
case 'float': return gl.RG32F;
case 'fp16': return gl.RG16F;
case 'int': return gl.RG32I;
}
case 'rgb':
switch (type) {
case 'ubyte': return gl.RGB;
@@ -124,7 +112,6 @@ function getByteCount(format: TextureFormat, type: TextureType, width: number, h
function getFormatSize(format: TextureFormat) {
switch (format) {
case 'alpha': return 1;
case 'rg': return 2;
case 'rgb': return 3;
case 'rgba': return 4;
case 'depth': return 4;

View File

@@ -35,7 +35,7 @@ function Vec3() {
namespace Vec3 {
export function zero(): Vec3 {
const out = [0.1, 0.0, 0.0]; // ensure backing array of type double
const out = [0.1, 0.0, 0.0];
out[0] = 0;
return out as any;
}

View File

@@ -32,7 +32,6 @@ const DnaAtomIdsList = [
/** Used to reduce false positives for atom name-based type guessing */
const NonPolymerNames = new Set([
'FMN', 'NCN', 'FNS', 'FMA', 'ATP', 'ADP', 'AMP', 'GTP', 'GDP', 'GMP', // Mononucleotides
'LIG'
]);
const StandardComponents = (function () {

View File

@@ -39,7 +39,7 @@ export function getAtomSiteTemplate(data: string, count: number) {
};
}
export function getAtomSite(sites: AtomSiteTemplate, terIndices: Set<number>): { [K in keyof mmCIF_Schema['atom_site'] | 'partial_charge']?: CifField } {
export function getAtomSite(sites: AtomSiteTemplate, hasTer: boolean): { [K in keyof mmCIF_Schema['atom_site'] | 'partial_charge']?: CifField } {
const pdbx_PDB_model_num = CifField.ofStrings(sites.pdbx_PDB_model_num);
const auth_asym_id = CifField.ofTokens(sites.auth_asym_id);
const auth_seq_id = CifField.ofTokens(sites.auth_seq_id);
@@ -67,17 +67,21 @@ export function getAtomSite(sites: AtomSiteTemplate, terIndices: Set<number>): {
const seqId = auth_seq_id.int(i);
let atomId = auth_atom_id.str(i);
let asymIdChanged = false;
if (modelNum !== currModelNum) {
asymIdCounts.clear();
atomIdCounts.clear();
currModelNum = modelNum;
currAsymId = asymId;
currSeqId = seqId;
asymIdChanged = true;
currLabelAsymId = asymId;
} else if (currAsymId !== asymId) {
atomIdCounts.clear();
currAsymId = asymId;
currSeqId = seqId;
asymIdChanged = true;
currLabelAsymId = asymId;
} else if (currSeqId !== seqId) {
atomIdCounts.clear();
@@ -87,7 +91,7 @@ export function getAtomSite(sites: AtomSiteTemplate, terIndices: Set<number>): {
if (asymIdCounts.has(asymId)) {
// only change the chains name if there are TER records
// otherwise assume repeated chain name use is from interleaved chains
if (terIndices.has(i)) {
if (hasTer && asymIdChanged) {
const asymIdCount = asymIdCounts.get(asymId)! + 1;
asymIdCounts.set(asymId, asymIdCount);
currLabelAsymId = `${asymId}_${asymIdCount}`;

View File

@@ -51,7 +51,7 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
let modelNum = 0, modelStr = '';
let conectRange: [number, number] | undefined = undefined;
const terIndices = new Set<number>();
let hasTer = false;
for (let i = 0, _i = lines.count; i < _i; i++) {
let s = indices[2 * i], e = indices[2 * i + 1];
@@ -164,7 +164,7 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
break;
case 'T':
if (substringStartsWith(data, s, e, 'TER')) {
terIndices.add(atomSite.index);
hasTer = true;
}
}
}
@@ -183,7 +183,7 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
atomSite.label_entity_id[i] = entityBuilder.getEntityId(compId, moleculeType, asymIds.value(i));
}
const atom_site = getAtomSite(atomSite, terIndices);
const atom_site = getAtomSite(atomSite, hasTer);
if (!isPdbqt) delete atom_site.partial_charge;
if (conectRange) {

View File

@@ -65,7 +65,7 @@ export namespace ComponentBond {
return e;
}
const { comp_id, atom_id_1, atom_id_2, value_order, pdbx_aromatic_flag, _rowCount, pdbx_ordinal } = data;
const { comp_id, atom_id_1, atom_id_2, value_order, pdbx_aromatic_flag, _rowCount } = data;
let entry = addEntry(comp_id.value(0)!);
for (let i = 0; i < _rowCount; i++) {
@@ -74,7 +74,6 @@ export namespace ComponentBond {
const nameB = atom_id_2.value(i)!;
const order = value_order.value(i)!;
const aromatic = pdbx_aromatic_flag.value(i) === 'y';
const key = pdbx_ordinal.value(i);
if (entry.id !== id) {
entry = addEntry(id);
@@ -90,29 +89,29 @@ export namespace ComponentBond {
case 'quad': ord = 4; break;
}
entry.add(nameA, nameB, ord, flags, key);
entry.add(nameA, nameB, ord, flags);
}
return entries;
}
export class Entry {
readonly map: Map<string, Map<string, { order: number, flags: number, key: number }>> = new Map();
readonly map: Map<string, Map<string, { order: number, flags: number }>> = new Map();
add(a: string, b: string, order: number, flags: number, key: number, swap = true) {
add(a: string, b: string, order: number, flags: number, swap = true) {
const e = this.map.get(a);
if (e !== void 0) {
const f = e.get(b);
if (f === void 0) {
e.set(b, { order, flags, key });
e.set(b, { order, flags });
}
} else {
const map = new Map<string, { order: number, flags: number, key: number }>();
map.set(b, { order, flags, key });
const map = new Map<string, { order: number, flags: number }>();
map.set(b, { order, flags });
this.map.set(a, map);
}
if (swap) this.add(b, a, order, flags, key, false);
if (swap) this.add(b, a, order, flags, false);
}
constructor(public readonly id: string) { }

View File

@@ -22,8 +22,6 @@ import { LocationIterator } from '../../../mol-geo/util/location-iterator';
import { InteractionFlag } from '../interactions/common';
import { Unit } from '../../../mol-model/structure/structure';
import { Sphere3D } from '../../../mol-math/geometry';
import { assertUnreachable } from '../../../mol-util/type-helpers';
import { InteractionsSharedParams } from './shared';
function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InteractionsInterUnitParams>, mesh?: Mesh) {
if (!structure.hasAtomic) return Mesh.createEmpty(mesh);
@@ -33,7 +31,7 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
const { contacts, unitsFeatures } = interactions;
const { edgeCount, edges } = contacts;
const { sizeFactor, parentDisplay } = props;
const { sizeFactor } = props;
if (!edgeCount) return Mesh.createEmpty(mesh);
@@ -72,48 +70,14 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
if (child) {
const b = edges[edgeIndex];
const childUnitA = child.unitMap.get(b.unitA);
if (!childUnitA) return true;
if (parentDisplay === 'stub') {
const childUnitA = child.unitMap.get(b.unitA);
if (!childUnitA) return true;
const unitA = structure.unitMap.get(b.unitA);
const { offsets, members } = unitsFeatures.get(b.unitA);
for (let i = offsets[b.indexA], il = offsets[b.indexA + 1]; i < il; ++i) {
const eA = unitA.elements[members[i]];
if (!SortedArray.has(childUnitA.elements, eA)) return true;
}
} else if (parentDisplay === 'full' || parentDisplay === 'between') {
let flagA = false;
let flagB = false;
const childUnitA = child.unitMap.get(b.unitA);
if (!childUnitA) {
flagA = true;
} else {
const unitA = structure.unitMap.get(b.unitA);
const { offsets, members } = unitsFeatures.get(b.unitA);
for (let i = offsets[b.indexA], il = offsets[b.indexA + 1]; i < il; ++i) {
const eA = unitA.elements[members[i]];
if (!SortedArray.has(childUnitA.elements, eA)) flagA = true;
}
}
const childUnitB = child.unitMap.get(b.unitB);
if (!childUnitB) {
flagB = true;
} else {
const unitB = structure.unitMap.get(b.unitB);
const { offsets, members } = unitsFeatures.get(b.unitB);
for (let i = offsets[b.indexB], il = offsets[b.indexB + 1]; i < il; ++i) {
const eB = unitB.elements[members[i]];
if (!SortedArray.has(childUnitB.elements, eB)) flagB = true;
}
}
return parentDisplay === 'full' ? flagA && flagB : flagA === flagB;
} else {
assertUnreachable(parentDisplay);
const unitA = structure.unitMap.get(b.unitA);
const { offsets, members } = unitsFeatures.get(b.unitA);
for (let i = offsets[b.indexA], il = offsets[b.indexA + 1]; i < il; ++i) {
const eA = unitA.elements[members[i]];
if (!SortedArray.has(childUnitA.elements, eA)) return true;
}
}
@@ -137,7 +101,10 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
export const InteractionsInterUnitParams = {
...ComplexMeshParams,
...LinkCylinderParams,
...InteractionsSharedParams,
sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
dashCount: PD.Numeric(6, { min: 2, max: 10, step: 2 }),
dashScale: PD.Numeric(0.4, { min: 0, max: 2, step: 0.1 }),
includeParent: PD.Boolean(false),
};
export type InteractionsInterUnitParams = typeof InteractionsInterUnitParams
@@ -154,8 +121,7 @@ export function InteractionsInterUnitVisual(materialId: number): ComplexVisual<I
newProps.dashCount !== currentProps.dashCount ||
newProps.dashScale !== currentProps.dashScale ||
newProps.dashCap !== currentProps.dashCap ||
newProps.radialSegments !== currentProps.radialSegments ||
newProps.parentDisplay !== currentProps.parentDisplay
newProps.radialSegments !== currentProps.radialSegments
);
const interactionsHash = InteractionsProvider.get(newStructure).version;

View File

@@ -22,8 +22,6 @@ import { Interactions } from '../interactions/interactions';
import { InteractionFlag } from '../interactions/common';
import { Sphere3D } from '../../../mol-math/geometry';
import { StructureGroup } from '../../../mol-repr/structure/visual/util/common';
import { assertUnreachable } from '../../../mol-util/type-helpers';
import { InteractionsSharedParams } from './shared';
async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<InteractionsIntraUnitParams>, mesh?: Mesh) {
if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh);
@@ -40,7 +38,7 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit:
const { x, y, z, members, offsets } = features;
const { edgeCount, a, b, edgeProps: { flag } } = contacts;
const { sizeFactor, parentDisplay } = props;
const { sizeFactor } = props;
if (!edgeCount) return Mesh.createEmpty(mesh);
@@ -62,31 +60,10 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit:
if (flag[edgeIndex] === InteractionFlag.Filtered) return true;
if (childUnit) {
if (parentDisplay === 'stub') {
const f = a[edgeIndex];
for (let i = offsets[f], il = offsets[f + 1]; i < il; ++i) {
const e = unit.elements[members[offsets[i]]];
if (!SortedArray.has(childUnit.elements, e)) return true;
}
} else if (parentDisplay === 'full' || parentDisplay === 'between') {
let flagA = false;
let flagB = false;
const fA = a[edgeIndex];
for (let i = offsets[fA], il = offsets[fA + 1]; i < il; ++i) {
const eA = unit.elements[members[offsets[i]]];
if (!SortedArray.has(childUnit.elements, eA)) flagA = true;
}
const fB = b[edgeIndex];
for (let i = offsets[fB], il = offsets[fB + 1]; i < il; ++i) {
const eB = unit.elements[members[offsets[i]]];
if (!SortedArray.has(childUnit.elements, eB)) flagB = true;
}
return parentDisplay === 'full' ? flagA && flagB : flagA === flagB;
} else {
assertUnreachable(parentDisplay);
const f = a[edgeIndex];
for (let i = offsets[f], jl = offsets[f + 1]; i < jl; ++i) {
const e = unit.elements[members[offsets[i]]];
if (!SortedArray.has(childUnit.elements, e)) return true;
}
}
@@ -109,7 +86,10 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit:
export const InteractionsIntraUnitParams = {
...UnitsMeshParams,
...LinkCylinderParams,
...InteractionsSharedParams,
sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
dashCount: PD.Numeric(6, { min: 2, max: 10, step: 2 }),
dashScale: PD.Numeric(0.4, { min: 0, max: 2, step: 0.1 }),
includeParent: PD.Boolean(false),
};
export type InteractionsIntraUnitParams = typeof InteractionsIntraUnitParams
@@ -126,8 +106,7 @@ export function InteractionsIntraUnitVisual(materialId: number): UnitsVisual<Int
newProps.dashCount !== currentProps.dashCount ||
newProps.dashScale !== currentProps.dashScale ||
newProps.dashCap !== currentProps.dashCap ||
newProps.radialSegments !== currentProps.radialSegments ||
newProps.parentDisplay !== currentProps.parentDisplay
newProps.radialSegments !== currentProps.radialSegments
);
const interactionsHash = InteractionsProvider.get(newStructureGroup.structure).version;

View File

@@ -1,16 +0,0 @@
/**
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
export const InteractionsSharedParams = {
sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
dashCount: PD.Numeric(6, { min: 2, max: 10, step: 2 }),
dashScale: PD.Numeric(0.4, { min: 0, max: 2, step: 0.1 }),
includeParent: PD.Boolean(false),
parentDisplay: PD.Select('stub', PD.arrayToOptions(['stub', 'full', 'between'] as const), { description: 'Only has an effect when "includeParent" is enabled. "Stub" shows just the child side of interactions to the parent. "Full" shows both sides of interactions to the parent. "Between" shows only interactions to the parent.' }),
};
export type InteractionsSharedParams = typeof InteractionsSharedParams

View File

@@ -12,7 +12,6 @@ import * as modifiers from './query/queries/modifiers';
import * as filters from './query/queries/filters';
import * as combinators from './query/queries/combinators';
import * as internal from './query/queries/internal';
import * as atomset from './query/queries/atom-set';
import { Predicates as pred } from './query/predicates';
export const Queries = {
@@ -21,8 +20,7 @@ export const Queries = {
modifiers,
combinators,
pred,
internal,
atomset
internal
};
export { StructureSelection, StructureQuery };
export { StructureSelection, StructureQuery };

View File

@@ -1,8 +1,7 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Structure, StructureElement, Unit } from '../structure';
@@ -114,7 +113,6 @@ class QueryContextBondInfo<U extends Unit = Unit> {
bIndex: StructureElement.UnitIndex = 0 as StructureElement.UnitIndex;
type: BondType = BondType.Flag.None;
order: number = 0;
key: number = -1;
private testFn: QueryPredicate = defaultBondTest;

View File

@@ -1,36 +0,0 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Koya Sakuma
* Adapted from MolQL implemtation of atom-set.ts
*
* Copyright (c) 2017 MolQL contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { StructureQuery } from '../query';
import { StructureSelection } from '../selection';
import { getCurrentStructureProperties } from './filters';
import { QueryContext, QueryFn } from '../context';
export function atomCount(ctx: QueryContext) {
return ctx.currentStructure.elementCount;
}
export function countQuery(query: StructureQuery) {
return (ctx: QueryContext) => {
const sel = query(ctx);
return StructureSelection.structureCount(sel);
};
}
export function propertySet(prop: QueryFn<any>) {
return (ctx: QueryContext) => {
const set = new Set();
return getCurrentStructureProperties(ctx, prop, set);
};
}

View File

@@ -1,13 +1,12 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018 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 { SetUtils } from '../../../../mol-util/set';
import { Unit } from '../../structure';
import { QueryContext, QueryFn } from '../context';
import { QueryContext, QueryFn, QueryPredicate } from '../context';
import { StructureQuery } from '../query';
import { StructureSelection } from '../selection';
import { structureAreIntersecting } from '../utils/structure-set';
@@ -17,7 +16,7 @@ import { Structure } from '../../structure/structure';
import { StructureElement } from '../../structure/element';
import { SortedArray } from '../../../../mol-data/int';
export function pick(query: StructureQuery, pred: QueryFn<any>): StructureQuery {
export function pick(query: StructureQuery, pred: QueryPredicate): StructureQuery {
return ctx => {
const sel = query(ctx);
const ret = StructureSelection.LinearBuilder(ctx.inputStructure);
@@ -51,7 +50,9 @@ export function first(query: StructureQuery): StructureQuery {
};
}
export function getCurrentStructureProperties(ctx: QueryContext, props: QueryFn<any>, set: Set<any>) {
export interface UnitTypeProperties { atomic?: QueryFn, coarse?: QueryFn }
export function getCurrentStructureProperties(ctx: QueryContext, props: UnitTypeProperties, set: Set<any>) {
const { units } = ctx.currentStructure;
const l = ctx.pushCurrentElement();
@@ -60,9 +61,9 @@ export function getCurrentStructureProperties(ctx: QueryContext, props: QueryFn<
l.unit = unit;
const elements = unit.elements;
const fn = props;
// if (Unit.isAtomic(unit)) fn = props.atomic;
// else fn = props.coarse;
let fn;
if (Unit.isAtomic(unit)) fn = props.atomic;
else fn = props.coarse;
if (!fn) continue;
for (let j = 0, _j = elements.length; j < _j; j++) {
@@ -76,7 +77,7 @@ export function getCurrentStructureProperties(ctx: QueryContext, props: QueryFn<
return set;
}
function getSelectionProperties(ctx: QueryContext, query: StructureQuery, props: QueryFn<any>) {
function getSelectionProperties(ctx: QueryContext, query: StructureQuery, props: UnitTypeProperties) {
const set = new Set();
const sel = query(ctx);
@@ -91,7 +92,7 @@ function getSelectionProperties(ctx: QueryContext, query: StructureQuery, props:
return set;
}
export function withSameAtomProperties(query: StructureQuery, propertySource: StructureQuery, props: QueryFn<any>): StructureQuery {
export function withSameAtomProperties(query: StructureQuery, propertySource: StructureQuery, props: UnitTypeProperties): StructureQuery {
return ctx => {
const sel = query(ctx);
const propSet = getSelectionProperties(ctx, propertySource, props);
@@ -101,7 +102,7 @@ export function withSameAtomProperties(query: StructureQuery, propertySource: St
StructureSelection.forEach(sel, (s, i) => {
ctx.currentStructure = s;
const currentProps = getCurrentStructureProperties(ctx, props, new Set());
if (SetUtils.isSuperset(propSet, currentProps)) {
if (SetUtils.isSuperset(currentProps, propSet)) {
ret.add(s);
}
@@ -247,7 +248,7 @@ function checkConnected(ctx: IsConnectedToCtx, structure: Structure) {
const inputUnit = input.unitMap.get(unit.id) as Unit.Atomic;
const { offset, b, edgeProps: { flags, order, key } } = inputUnit.bonds;
const { offset, b, edgeProps: { flags, order } } = inputUnit.bonds;
const bondedUnits = interBonds.getConnectedUnits(unit.id);
const buCount = bondedUnits.length;
@@ -272,7 +273,6 @@ function checkConnected(ctx: IsConnectedToCtx, structure: Structure) {
atomicBond.bIndex = b[l] as StructureElement.UnitIndex;
atomicBond.type = flags[l];
atomicBond.order = order[l];
atomicBond.key = key[l];
if (atomicBond.test(queryCtx, true)) return true;
}
@@ -295,7 +295,6 @@ function checkConnected(ctx: IsConnectedToCtx, structure: Structure) {
atomicBond.bIndex = bond.indexB;
atomicBond.type = bond.props.flag;
atomicBond.order = bond.props.order;
atomicBond.key = bond.props.key;
if (atomicBond.test(queryCtx, true)) return true;
}
}
@@ -343,4 +342,4 @@ export function isConnectedTo({ query, target, disjunct, invert, bondTest }: IsC
return ret.getSelection();
};
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2022 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>
@@ -322,7 +322,7 @@ export function bondedAtomicPairs(bondTest?: QueryPredicate): StructureQuery {
for (const unit of structure.units) {
if (unit.kind !== Unit.Kind.Atomic) continue;
const { offset: intraBondOffset, b: intraBondB, edgeProps: { flags, order, key } } = unit.bonds;
const { offset: intraBondOffset, b: intraBondB, edgeProps: { flags, order } } = unit.bonds;
atomicBond.a.unit = unit;
atomicBond.b.unit = unit;
for (let i = 0 as StructureElement.UnitIndex, _i = unit.elements.length; i < _i; i++) {
@@ -335,7 +335,6 @@ export function bondedAtomicPairs(bondTest?: QueryPredicate): StructureQuery {
atomicBond.b.element = unit.elements[intraBondB[lI]];
atomicBond.type = flags[lI];
atomicBond.order = order[lI];
atomicBond.key = key[lI];
// No need to "swap test" because each bond direction will be visited eventually.
if (atomicBond.test(ctx, false)) {
const b = structure.subsetBuilder(false);
@@ -359,7 +358,6 @@ export function bondedAtomicPairs(bondTest?: QueryPredicate): StructureQuery {
atomicBond.bIndex = bond.indexB;
atomicBond.order = bond.props.order;
atomicBond.type = bond.props.flag;
atomicBond.key = bond.props.key;
// No need to "swap test" because each bond direction will be visited eventually.
if (atomicBond.test(ctx, false)) {

View File

@@ -1,8 +1,7 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Segmentation, SortedArray } from '../../../../mol-data/int';
@@ -371,7 +370,7 @@ function expandConnected(ctx: QueryContext, structure: Structure) {
}
const inputUnitA = inputStructure.unitMap.get(unit.id) as Unit.Atomic;
const { offset: intraBondOffset, b: intraBondB, edgeProps: { flags, order, key } } = inputUnitA.bonds;
const { offset: intraBondOffset, b: intraBondB, edgeProps: { flags, order } } = inputUnitA.bonds;
atomicBond.setStructure(inputStructure);
@@ -398,7 +397,6 @@ function expandConnected(ctx: QueryContext, structure: Structure) {
atomicBond.b.element = bElement;
atomicBond.type = flags[lI];
atomicBond.order = order[lI];
atomicBond.key = key[lI];
if (atomicBond.test(ctx, true)) {
builder.addToUnit(unit.id, bElement);
@@ -429,7 +427,6 @@ function expandConnected(ctx: QueryContext, structure: Structure) {
atomicBond.b.element = bElement;
atomicBond.type = bond.props.flag;
atomicBond.order = bond.props.order;
atomicBond.key = bond.props.key;
if (atomicBond.test(ctx, true)) {
builder.addToUnit(bondedUnit.unitB, bElement);

View File

@@ -15,17 +15,16 @@ import { InterUnitGraph } from '../../../../../mol-math/graph/inter-unit-graph';
type IntraUnitBonds = IntAdjacencyGraph<StructureElement.UnitIndex, {
readonly order: ArrayLike<number>,
readonly flags: ArrayLike<BondType.Flag>
readonly key: ArrayLike<number>,
}, {
/** can remap even with dynamicBonds on, e.g., for water molecules */
readonly canRemap?: boolean
}>
namespace IntraUnitBonds {
export const Empty: IntraUnitBonds = IntAdjacencyGraph.create([], [], [], 0, { flags: [], order: [], key: [] });
export const Empty: IntraUnitBonds = IntAdjacencyGraph.create([], [], [], 0, { flags: [], order: [] });
}
type InterUnitEdgeProps = { readonly order: number, readonly flag: BondType.Flag, readonly key: number }
type InterUnitEdgeProps = { readonly order: number, readonly flag: BondType.Flag }
class InterUnitBonds extends InterUnitGraph<number, StructureElement.UnitIndex, InterUnitEdgeProps> {
/** Get inter-unit bond given a bond-location */

View File

@@ -80,7 +80,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
if (!props.forceCompute && indexPairs) {
const { maxDistance } = indexPairs;
const { offset, b, edgeProps: { order, distance, flag, key } } = indexPairs.bonds;
const { offset, b, edgeProps: { order, distance, flag } } = indexPairs.bonds;
const srcA = sourceIndex.value(aI);
const aeI = getElementIdx(type_symbolA.value(aI));
@@ -113,7 +113,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
}
if (add) {
builder.add(_aI, _bI, { order: order[i], flag: flag[i], key: key[i] });
builder.add(_aI, _bI, { order: order[i], flag: flag[i] });
}
}
continue; // assume `indexPairs` supplies all bonds
@@ -131,7 +131,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
// check if the bond is within MAX_RADIUS for this pair of units
if (getDistance(unitA, aI, unitB, p.atomIndex) > maxRadius) continue;
builder.add(_aI, _bI, { order: se.order, flag: se.flags, key: se.rowIndex });
builder.add(_aI, _bI, { order: se.order, flag: se.flags });
added = true;
}
// assume, for an atom, that if any inter unit bond is given
@@ -187,8 +187,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
const compIdB = label_comp_idB.value(residueIndexB[bI]);
builder.add(_aI, _bI, {
order: getInterBondOrderFromTable(compIdA, compIdB, atomIdA, atomIdB),
flag: (isMetal ? BondType.Flag.MetallicCoordination : BondType.Flag.Covalent) | BondType.Flag.Computed,
key: -1
flag: (isMetal ? BondType.Flag.MetallicCoordination : BondType.Flag.Covalent) | BondType.Flag.Computed
});
}
}

View File

@@ -24,19 +24,17 @@ import { Model } from '../../../model/model';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3distance = Vec3.distance;
function getGraph(atomA: StructureElement.UnitIndex[], atomB: StructureElement.UnitIndex[], _order: number[], _flags: number[], _key: number[], atomCount: number, canRemap: boolean): IntraUnitBonds {
function getGraph(atomA: StructureElement.UnitIndex[], atomB: StructureElement.UnitIndex[], _order: number[], _flags: number[], atomCount: number, canRemap: boolean): IntraUnitBonds {
const builder = new IntAdjacencyGraph.EdgeBuilder(atomCount, atomA, atomB);
const flags = new Uint16Array(builder.slotCount);
const order = new Int8Array(builder.slotCount);
const key = new Uint32Array(builder.slotCount);
for (let i = 0, _i = builder.edgeCount; i < _i; i++) {
builder.addNextEdge();
builder.assignProperty(flags, _flags[i]);
builder.assignProperty(order, _order[i]);
builder.assignProperty(key, _key[i]);
}
return builder.createGraph({ flags, order, key }, { canRemap });
return builder.createGraph({ flags, order }, { canRemap });
}
const tmpDistVecA = Vec3();
@@ -55,7 +53,7 @@ function findIndexPairBonds(unit: Unit.Atomic) {
const { type_symbol } = unit.model.atomicHierarchy.atoms;
const atomCount = unit.elements.length;
const { maxDistance } = indexPairs;
const { offset, b, edgeProps: { order, distance, flag, key } } = indexPairs.bonds;
const { offset, b, edgeProps: { order, distance, flag } } = indexPairs.bonds;
const { atomSourceIndex: sourceIndex } = unit.model.atomicHierarchy;
const { invertedIndex } = Model.getInvertedAtomSourceIndex(unit.model);
@@ -64,7 +62,6 @@ function findIndexPairBonds(unit: Unit.Atomic) {
const atomB: StructureElement.UnitIndex[] = [];
const flags: number[] = [];
const orders: number[] = [];
const keys: number[] = [];
for (let _aI = 0 as StructureElement.UnitIndex; _aI < atomCount; _aI++) {
const aI = atoms[_aI];
@@ -107,12 +104,11 @@ function findIndexPairBonds(unit: Unit.Atomic) {
atomB[atomB.length] = _bI;
orders[orders.length] = order[i];
flags[flags.length] = flag[i];
keys[keys.length] = key[i];
}
}
}
return getGraph(atomA, atomB, orders, flags, keys, atomCount, false);
return getGraph(atomA, atomB, orders, flags, atomCount, false);
}
function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBonds {
@@ -136,10 +132,9 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
const atomB: StructureElement.UnitIndex[] = [];
const flags: number[] = [];
const order: number[] = [];
const key: number[] = [];
let lastResidue = -1;
let componentMap: Map<string, Map<string, { flags: number, order: number, key: number }>> | undefined = void 0;
let componentMap: Map<string, Map<string, { flags: number, order: number }>> | undefined = void 0;
let isWatery = true, isDictionaryBased = true, isSequenced = true;
@@ -167,7 +162,6 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
atomB[atomB.length] = _bI;
flags[flags.length] = se.flags;
order[order.length] = se.order;
key[key.length] = se.rowIndex;
if (!hasStructConn) structConnAdded.clear();
hasStructConn = true;
@@ -236,7 +230,6 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
flag |= BondType.Flag.MetallicCoordination;
}
flags[flags.length] = flag;
key[key.length] = e.key;
}
continue;
}
@@ -250,7 +243,6 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
atomB[atomB.length] = _bI;
order[order.length] = getIntraBondOrderFromTable(compId, atomIdA, label_atom_id.value(bI));
flags[flags.length] = (isMetal ? BondType.Flag.MetallicCoordination : BondType.Flag.Covalent) | BondType.Flag.Computed;
key[key.length] = -1;
const seqIdB = label_seq_id.value(rbI);
@@ -261,7 +253,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
}
const canRemap = isWatery || (isDictionaryBased && isSequenced);
return getGraph(atomA, atomB, order, flags, key, atomCount, canRemap);
return getGraph(atomA, atomB, order, flags, atomCount, canRemap);
}
function computeIntraUnitBonds(unit: Unit.Atomic, props?: Partial<BondComputationProps>) {

View File

@@ -8,8 +8,7 @@
import { Segmentation } from '../../../../mol-data/int';
import { MinimizeRmsd } from '../../../../mol-math/linear-algebra/3d/minimize-rmsd';
import { SIFTSMapping } from '../../../../mol-model-props/sequence/sifts-mapping';
import { ElementIndex, ResidueIndex } from '../../model/indexing';
import { StructureElement } from '../element';
import { ElementIndex } from '../../model/indexing';
import { Structure } from '../structure';
import { Unit } from '../unit';
@@ -25,16 +24,11 @@ export interface AlignmentResult {
failedPairs: [number, number][]
}
type IncludeResidueTest = (traceElementOrFirstAtom: StructureElement.Location<Unit.Atomic>, residueIndex: ResidueIndex, startIndex: ElementIndex, endIndex: ElementIndex) => boolean
export function alignAndSuperposeWithSIFTSMapping(
structures: Structure[],
options?: { traceOnly?: boolean, includeResidueTest?: IncludeResidueTest }
): AlignmentResult {
export function alignAndSuperposeWithSIFTSMapping(structures: Structure[], options?: { traceOnly?: boolean }): AlignmentResult {
const indexMap = new Map<string, IndexEntry>();
for (let i = 0; i < structures.length; i++) {
buildIndex(structures[i], indexMap, i, options?.traceOnly ?? true, options?.includeResidueTest ?? _includeAllResidues);
buildIndex(structures[i], indexMap, i, options?.traceOnly ?? true);
}
const index = Array.from(indexMap.values());
@@ -143,16 +137,11 @@ interface IndexEntry {
pivots: { [i: number]: [unit: Unit.Atomic, start: ElementIndex, end: ElementIndex] | undefined }
}
function _includeAllResidues() { return true; }
function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: number, traceOnly: boolean, includeTest: IncludeResidueTest) {
const loc = StructureElement.Location.create<Unit.Atomic>(structure);
function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: number, traceOnly: boolean) {
for (const unit of structure.units) {
if (unit.kind !== Unit.Kind.Atomic) continue;
const { elements, model } = unit;
loc.unit = unit;
const map = SIFTSMapping.Provider.get(model).value;
if (!map) return;
@@ -172,11 +161,9 @@ function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: nu
if (!dbName[rI]) continue;
const traceElement = traceElementIndex[rI];
let start, end;
if (traceOnly) {
start = traceElement;
start = traceElementIndex[rI];
if (start === -1) continue;
end = start + 1 as ElementIndex;
} else {
@@ -184,9 +171,6 @@ function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: nu
end = elements[residueSegment.end - 1] + 1 as ElementIndex;
}
loc.element = (traceElement >= 0 ? traceElement : start) as ElementIndex;
if (!includeTest(loc, rI, start, end)) continue;
const key = `${dbName[rI]}-${accession[rI]}-${num[rI]}`;
if (!index.has(key)) {

View File

@@ -90,7 +90,6 @@ const DownloadStructure = StateAction.build({
url: PD.Url(''),
format: PD.Select<BuiltInTrajectoryFormat>('mmcif', PD.arrayToOptions(BuiltInTrajectoryFormats.map(f => f[0]), f => f)),
isBinary: PD.Boolean(false),
label: PD.Optional(PD.Text('')),
options
}, { isFlat: true, label: 'URL' })
})
@@ -105,7 +104,7 @@ const DownloadStructure = StateAction.build({
switch (src.name) {
case 'url':
downloadParams = [{ url: src.params.url, isBinary: src.params.isBinary, label: src.params.label || undefined }];
downloadParams = [{ url: src.params.url, isBinary: src.params.isBinary }];
format = src.params.format;
break;
case 'pdb':

View File

@@ -41,10 +41,8 @@ export namespace StructureRepresentationPresetProvider {
quality: PD.Optional(PD.Select<VisualQuality>('auto', VisualQualityOptions)),
theme: PD.Optional(PD.Group({
globalName: PD.Optional(PD.Text<ColorTheme.BuiltIn>('')),
globalColorParams: PD.Optional(PD.Value<any>({}, { isHidden: true })),
carbonColor: PD.Optional(PD.Select('chain-id', PD.arrayToOptions(['chain-id', 'operator-name', 'element-symbol'] as const))),
symmetryColor: PD.Optional(PD.Text<ColorTheme.BuiltIn>('')),
symmetryColorParams: PD.Optional(PD.Value<any>({}, { isHidden: true })),
focus: PD.Optional(PD.Group({
name: PD.Optional(PD.Text<ColorTheme.BuiltIn>('')),
params: PD.Optional(PD.Value<ColorTheme.BuiltInParams<ColorTheme.BuiltIn>>({} as any))
@@ -78,15 +76,13 @@ export namespace StructureRepresentationPresetProvider {
if (params.ignoreLight !== void 0) typeParams.ignoreLight = !!params.ignoreLight;
const color: ColorTheme.BuiltIn | undefined = params.theme?.globalName ? params.theme?.globalName : void 0;
const ballAndStickColor: ColorTheme.BuiltInParams<'element-symbol'> = params.theme?.carbonColor !== undefined
? { carbonColor: getCarbonColorParams(params.theme?.carbonColor), ...params.theme?.globalColorParams }
: { ...params.theme?.globalColorParams };
? { carbonColor: getCarbonColorParams(params.theme?.carbonColor) }
: { };
const symmetryColor: ColorTheme.BuiltIn | undefined = structure && params.theme?.symmetryColor
? isSymmetry(structure) ? params.theme?.symmetryColor : color
: color;
const symmetryColorParams = params.theme?.symmetryColorParams ? { ...params.theme?.globalColorParams, ...params.theme?.symmetryColorParams } : { ...params.theme?.globalColorParams };
const globalColorParams = params.theme?.globalColorParams ? { ...params.theme?.globalColorParams } : undefined;
return { update, builder, color, symmetryColor, symmetryColorParams, globalColorParams, typeParams, ballAndStickColor };
return { update, builder, color, symmetryColor, typeParams, ballAndStickColor };
}
export function updateFocusRepr<T extends ColorTheme.BuiltIn>(plugin: PluginContext, structure: Structure, themeName: T | undefined, themeParams: ColorTheme.BuiltInParams<T> | undefined) {
@@ -181,18 +177,18 @@ const polymerAndLigand = StructureRepresentationPresetProvider({
const waterType = (components.water?.obj?.data?.elementCount || 0) > 50_000 ? 'line' : 'ball-and-stick';
const lipidType = (components.lipid?.obj?.data?.elementCount || 0) > 20_000 ? 'line' : 'ball-and-stick';
const { update, builder, typeParams, color, symmetryColor, symmetryColorParams, globalColorParams, ballAndStickColor } = reprBuilder(plugin, params, structure);
const { update, builder, typeParams, color, symmetryColor, ballAndStickColor } = reprBuilder(plugin, params, structure);
const representations = {
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'polymer' }),
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor }, { tag: 'polymer' }),
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams, color, colorParams: ballAndStickColor }, { tag: 'ligand' }),
nonStandard: builder.buildRepresentation(update, components.nonStandard, { type: 'ball-and-stick', typeParams, color, colorParams: ballAndStickColor }, { tag: 'non-standard' }),
branchedBallAndStick: builder.buildRepresentation(update, components.branched, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.3 }, color, colorParams: ballAndStickColor }, { tag: 'branched-ball-and-stick' }),
branchedSnfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams, color, colorParams: globalColorParams }, { tag: 'branched-snfg-3d' }),
water: builder.buildRepresentation(update, components.water, { type: waterType, typeParams: { ...typeParams, alpha: 0.6, visuals: waterType === 'line' ? ['intra-bond', 'element-point'] : undefined }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} }, ...globalColorParams } }, { tag: 'water' }),
ion: builder.buildRepresentation(update, components.ion, { type: 'ball-and-stick', typeParams, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} }, ...globalColorParams } }, { tag: 'ion' }),
lipid: builder.buildRepresentation(update, components.lipid, { type: lipidType, typeParams: { ...typeParams, alpha: 0.6, visuals: lipidType === 'line' ? ['intra-bond'] : undefined }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} }, ...globalColorParams } }, { tag: 'lipid' }),
coarse: builder.buildRepresentation(update, components.coarse, { type: 'spacefill', typeParams, color: color || 'chain-id', colorParams: globalColorParams }, { tag: 'coarse' })
branchedSnfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams, color }, { tag: 'branched-snfg-3d' }),
water: builder.buildRepresentation(update, components.water, { type: waterType, typeParams: { ...typeParams, alpha: 0.6, visuals: waterType === 'line' ? ['intra-bond', 'element-point'] : undefined }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'water' }),
ion: builder.buildRepresentation(update, components.ion, { type: 'ball-and-stick', typeParams, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ion' }),
lipid: builder.buildRepresentation(update, components.lipid, { type: lipidType, typeParams: { ...typeParams, alpha: 0.6, visuals: lipidType === 'line' ? ['intra-bond'] : undefined }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'lipid' }),
coarse: builder.buildRepresentation(update, components.coarse, { type: 'spacefill', typeParams, color: color || 'chain-id' }, { tag: 'coarse' })
};
await update.commit({ revertOnError: false });
@@ -227,11 +223,11 @@ const proteinAndNucleic = StructureRepresentationPresetProvider({
smoothness: structure.isCoarseGrained ? 1.0 : 1.5,
};
const { update, builder, typeParams, symmetryColor, symmetryColorParams } = reprBuilder(plugin, params, structure);
const { update, builder, typeParams, symmetryColor } = reprBuilder(plugin, params, structure);
const representations = {
protein: builder.buildRepresentation(update, components.protein, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'protein' }),
nucleic: builder.buildRepresentation(update, components.nucleic, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'nucleic' })
protein: builder.buildRepresentation(update, components.protein, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor }, { tag: 'protein' }),
nucleic: builder.buildRepresentation(update, components.nucleic, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor }, { tag: 'nucleic' })
};
await update.commit({ revertOnError: true });
@@ -279,11 +275,11 @@ const coarseSurface = StructureRepresentationPresetProvider({
});
}
const { update, builder, typeParams, symmetryColor, symmetryColorParams } = reprBuilder(plugin, params, structure);
const { update, builder, typeParams, symmetryColor } = reprBuilder(plugin, params, structure);
const representations = {
polymer: builder.buildRepresentation(update, components.polymer, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'polymer' }),
lipid: builder.buildRepresentation(update, components.lipid, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'lipid' })
polymer: builder.buildRepresentation(update, components.polymer, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor }, { tag: 'polymer' }),
lipid: builder.buildRepresentation(update, components.lipid, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor }, { tag: 'lipid' })
};
await update.commit({ revertOnError: true });
@@ -313,10 +309,10 @@ const polymerCartoon = StructureRepresentationPresetProvider({
sizeFactor: structure.isCoarseGrained ? 0.8 : 0.2
};
const { update, builder, typeParams, symmetryColor, symmetryColorParams } = reprBuilder(plugin, params, structure);
const { update, builder, typeParams, symmetryColor } = reprBuilder(plugin, params, structure);
const representations = {
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'polymer' })
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor }, { tag: 'polymer' })
};
await update.commit({ revertOnError: true });
@@ -371,9 +367,9 @@ const atomicDetail = StructureRepresentationPresetProvider({
});
}
const { update, builder, typeParams, color, ballAndStickColor, globalColorParams } = reprBuilder(plugin, params, structure);
const { update, builder, typeParams, color, ballAndStickColor } = reprBuilder(plugin, params, structure);
const colorParams = lowResidueElementRatio && !bondsGiven
? { carbonColor: { name: 'element-symbol', params: {} }, ...globalColorParams }
? { carbonColor: { name: 'element-symbol', params: {} } }
: ballAndStickColor;
const representations = {
@@ -381,7 +377,7 @@ const atomicDetail = StructureRepresentationPresetProvider({
};
if (showCarbohydrateSymbol) {
Object.assign(representations, {
snfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams: { ...typeParams, alpha: 0.4, visuals: ['carbohydrate-symbol'] }, color, colorParams: globalColorParams }, { tag: 'snfg-3d' }),
snfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams: { ...typeParams, alpha: 0.4, visuals: ['carbohydrate-symbol'] }, color }, { tag: 'snfg-3d' }),
});
}

View File

@@ -90,7 +90,7 @@ export class StructureRepresentationBuilder {
}
applyPreset<K extends keyof PresetStructureRepresentations>(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, preset: K, params?: StructureRepresentationPresetProvider.Params<PresetStructureRepresentations[K]>): Promise<StructureRepresentationPresetProvider.State<PresetStructureRepresentations[K]>> | undefined
applyPreset<P = any, S extends {} = {}>(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, provider: StructureRepresentationPresetProvider<P, S>, params?: P): Promise<S> | undefined
applyPreset<P = any, S = {}>(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, provider: StructureRepresentationPresetProvider<P, S>, params?: P): Promise<S> | undefined
applyPreset(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, providerId: string, params?: any): Promise<any> | undefined
applyPreset(parent: StateObjectRef, providerRef: string | StructureRepresentationPresetProvider, params?: any): Promise<any> | undefined {
const provider = this.resolveProvider(providerRef);

View File

@@ -42,7 +42,7 @@ export class PluginComponent {
}
}
export class StatefulPluginComponent<State extends {}> extends PluginComponent {
export class StatefulPluginComponent<State> extends PluginComponent {
private _state: State;
protected updateState(...states: Partial<State>[]): boolean {

View File

@@ -75,7 +75,7 @@ export const CifCoreProvider: TrajectoryFormatProvider = {
visuals: defaultVisuals
};
function directTrajectory<P extends {}>(transformer: StateTransformer<PluginStateObject.Data.String | PluginStateObject.Data.Binary, PluginStateObject.Molecule.Trajectory, P>, transformerParams?: P): TrajectoryFormatProvider['parse'] {
function directTrajectory<P>(transformer: StateTransformer<PluginStateObject.Data.String | PluginStateObject.Data.Binary, PluginStateObject.Molecule.Trajectory, P>, transformerParams?: P): TrajectoryFormatProvider['parse'] {
return async (plugin, data, params) => {
const state = plugin.state.data;
const trajectory = await state.build().to(data)

View File

@@ -1079,4 +1079,4 @@ const ShapeFromPly = PluginStateTransform.BuiltIn({
return new SO.Shape.Provider(shape, props);
});
}
});
});

View File

@@ -13,7 +13,7 @@ import { Icon, ArrowRightSvg, ArrowDropDownSvg } from './controls/icons';
export const PluginReactContext = React.createContext(void 0 as any as PluginUIContext);
export abstract class PluginUIComponent<P extends {} = {}, S = {}, SS = {}> extends React.Component<P & { children?: any }, S, SS> {
export abstract class PluginUIComponent<P = {}, S = {}, SS = {}> extends React.Component<P & { children?: any }, S, SS> {
static contextType = PluginReactContext;
readonly plugin: PluginUIContext;

View File

@@ -7,7 +7,6 @@
import * as React from 'react';
import { Mat4, Vec2, Vec3 } from '../../mol-math/linear-algebra';
import { Script } from '../../mol-script/script';
import { Asset } from '../../mol-util/assets';
import { Color } from '../../mol-util/color';
import { ColorListEntry } from '../../mol-util/color/color';
@@ -23,7 +22,7 @@ import { PluginUIContext } from '../context';
import { ActionMenu } from './action-menu';
import { ColorOptions, ColorValueOption, CombinedColorControl } from './color';
import { Button, ControlGroup, ControlRow, ExpandGroup, IconButton, TextInput, ToggleButton } from './common';
import { ArrowDownwardSvg, ArrowDropDownSvg, ArrowRightSvg, ArrowUpwardSvg, BookmarksOutlinedSvg, CheckSvg, ClearSvg, DeleteOutlinedSvg, HelpOutlineSvg, Icon, MoreHorizSvg, WarningSvg } from './icons';
import { ArrowDownwardSvg, ArrowDropDownSvg, ArrowRightSvg, ArrowUpwardSvg, BookmarksOutlinedSvg, CheckSvg, ClearSvg, DeleteOutlinedSvg, HelpOutlineSvg, Icon, MoreHorizSvg } from './icons';
import { legendFor } from './legend';
import { LineGraphComponent } from './line-graph/line-graph-component';
import { Slider, Slider2 } from './slider';
@@ -1467,38 +1466,31 @@ export class ConvertedControl extends React.PureComponent<ParamProps<PD.Converte
}
}
export class ScriptControl extends React.PureComponent<ParamProps<PD.Script>> {
onChange: ParamOnChange = ({ name, value }) => {
const k = name as 'language' | 'expression';
if (value !== this.props.value[k]) {
this.props.onChange({ param: this.props.param, name: this.props.name, value: { ...this.props.value, [k]: value } });
export class ScriptControl extends SimpleParam<PD.Script> {
onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
if (value !== this.props.value.expression) {
this.update({ language: this.props.value.language, expression: value });
}
};
render() {
onKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
if ((e.keyCode === 13 || e.charCode === 13 || e.key === 'Enter')) {
if (this.props.onEnter) this.props.onEnter();
}
e.stopPropagation();
};
renderControl() {
// TODO: improve!
const selectParam: PD.Select<PD.Script['defaultValue']['language']> = {
defaultValue: this.props.value.language,
options: PD.objectToOptions(Script.Info),
type: 'select',
};
const select = <SelectControl param={selectParam}
isDisabled={this.props.isDisabled} onChange={this.onChange} onEnter={this.props.onEnter}
name='language' value={this.props.value.language} />;
const textParam: PD.Text = {
defaultValue: this.props.value.language,
type: 'text',
};
const text = <TextControl param={textParam} isDisabled={this.props.isDisabled} onChange={this.onChange} name='expression' value={this.props.value.expression} />;
return <>
{select}
{this.props.value.language !== 'mol-script' && <div className='msp-help-text' style={{ padding: '10px' }}>
<Icon svg={WarningSvg} /> Support for PyMOL, VMD, and Jmol selections is an experimental feature and may not always work as intended.
</div>}
{text}
</>;
const placeholder = this.props.param.label || camelCaseToWords(this.props.name);
return <input type='text'
value={this.props.value.expression || ''}
placeholder={placeholder}
onChange={this.onChange}
onKeyPress={this.props.onEnter ? this.onKeyPress : void 0}
disabled={this.props.isDisabled}
/>;
}
}
}

View File

@@ -1,8 +1,7 @@
/**
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react';
@@ -26,7 +25,7 @@ export interface ScreenshotPreviewProps {
const _ScreenshotPreview = (props: ScreenshotPreviewProps) => {
const { plugin, cropFrameColor } = props;
const helper = plugin.helpers.viewportScreenshot;
const helper = plugin.helpers.viewportScreenshot!;
const [currentCanvas, setCurrentCanvas] = useState<HTMLCanvasElement | null>(null);
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const propsRef = useRef(props);
@@ -71,8 +70,8 @@ const _ScreenshotPreview = (props: ScreenshotPreviewProps) => {
subscribe(plugin.state.data.behaviors.isUpdating, v => {
if (!v) isDirty = true;
});
subscribe(helper?.behaviors.values, () => isDirty = true);
subscribe(helper?.behaviors.cropParams, () => isDirty = true);
subscribe(helper.behaviors.values, () => isDirty = true);
subscribe(helper.behaviors.cropParams, () => isDirty = true);
let resizeObserver: any = void 0;
if (typeof ResizeObserver !== 'undefined') {
@@ -109,9 +108,7 @@ export const ScreenshotPreview = React.memo(_ScreenshotPreview, (prev, next) =>
declare const ResizeObserver: any;
function drawPreview(helper: ViewportScreenshotHelper | undefined, target: HTMLCanvasElement, customBackground?: string, borderColor?: string, borderWidth?: number) {
if (!helper) return;
function drawPreview(helper: ViewportScreenshotHelper, target: HTMLCanvasElement, customBackground?: string, borderColor?: string, borderWidth?: number) {
const { canvas, width, height } = helper.getPreview()!;
const ctx = target.getContext('2d');
if (!ctx) return;
@@ -154,9 +151,9 @@ function drawPreview(helper: ViewportScreenshotHelper | undefined, target: HTMLC
function ViewportFrame({ plugin, canvas, color = 'rgba(255, 87, 45, 0.75)' }: { plugin: PluginContext, canvas: HTMLCanvasElement | null, color?: string }) {
const helper = plugin.helpers.viewportScreenshot;
const params = useBehavior(helper?.behaviors.values);
const cropParams = useBehavior(helper?.behaviors.cropParams);
const crop = useBehavior(helper?.behaviors.relativeCrop);
const params = useBehavior(helper?.behaviors.values!);
const cropParams = useBehavior(helper?.behaviors.cropParams!);
const crop = useBehavior(helper?.behaviors.relativeCrop!);
const cropFrameRef = useRef<Viewport>({ x: 0, y: 0, width: 0, height: 0 });
useBehavior(params?.resolution.name === 'viewport' ? plugin.canvas3d?.resized : void 0);
@@ -164,7 +161,7 @@ function ViewportFrame({ plugin, canvas, color = 'rgba(255, 87, 45, 0.75)' }: {
const [start, setStart] = useState([0, 0]);
const [current, setCurrent] = useState([0, 0]);
if (!helper || !canvas || !crop) return null;
if (!helper || !canvas) return null;
const { width, height } = helper.getSizeAndViewport();
@@ -270,7 +267,7 @@ function ViewportFrame({ plugin, canvas, color = 'rgba(255, 87, 45, 0.75)' }: {
function finish() {
const cropFrame = cropFrameRef.current;
if (cropParams?.auto) {
if (cropParams.auto) {
helper?.behaviors.cropParams.next({ ...cropParams, auto: false });
}
helper?.behaviors.relativeCrop.next({

View File

@@ -1,8 +1,7 @@
/**
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Adam Midlik <midlik@gmail.com>
*/
import { PluginUIComponent } from '../base';
@@ -200,9 +199,6 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
const viewParams = { ...oldView };
if (value.name === 'selection-box') {
viewParams.radius = value.params.radius;
} else if (value.name === 'camera-target') {
viewParams.radius = value.params.radius;
viewParams.dynamicDetailLevel = value.params.dynamicDetailLevel;
} else if (value.name === 'box') {
viewParams.bottomLeft = value.params.bottomLeft;
viewParams.topRight = value.params.topRight;
@@ -244,23 +240,13 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
const pivot = isEM ? 'em' : '2fo-fc';
const params = this.props.params as VolumeStreaming.Params;
const entry = (this.props.info.params as VolumeStreaming.ParamDefinition)
.entry.map(params.entry.name) as PD.Group<VolumeStreaming.EntryParamDefinition>;
const entry = ((this.props.info.params as VolumeStreaming.ParamDefinition)
.entry.map(params.entry.name) as PD.Group<VolumeStreaming.EntryParamDefinition>);
const detailLevel = entry.params.detailLevel;
const dynamicDetailLevel = {
...detailLevel,
label: 'Dynamic Detail',
defaultValue: (entry.params.view as any).map('camera-target').params.dynamicDetailLevel.defaultValue,
};
const selectionDetailLevel = {
...detailLevel,
label: 'Selection Detail',
defaultValue: (entry.params.view as any).map('auto').params.selectionDetailLevel.defaultValue,
};
const isRelative = ((params.entry.params.channels as any)[pivot].isoValue as Volume.IsoValue).kind === 'relative';
const sampling = b.info.header.sampling[0];
const isRelative = ((params.entry.params.channels as any)[pivot].isoValue as Volume.IsoValue).kind === 'relative';
const isRelativeParam = PD.Boolean(isRelative, { description: 'Use normalized or absolute isocontour scale.', label: 'Normalized' });
const isUnbounded = !!(params.entry.params.view.params as any).isUnbounded;
@@ -288,13 +274,6 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
isRelative: isRelativeParam,
isUnbounded: isUnboundedParam,
}, { description: 'Box around focused element.' }),
'camera-target': PD.Group({
radius: PD.Numeric(0.5, { min: 0, max: 1, step: 0.05 }, { description: 'Radius within which the volume is shown (relative to the field of view).' }),
detailLevel: { ...detailLevel, isHidden: true },
dynamicDetailLevel: dynamicDetailLevel,
isRelative: isRelativeParam,
isUnbounded: isUnboundedParam,
}, { description: 'Box around camera target.' }),
'cell': PD.Group({
detailLevel,
isRelative: isRelativeParam,
@@ -303,11 +282,12 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
'auto': PD.Group({
radius: PD.Numeric(5, { min: 0, max: 50, step: 0.5 }, { description: 'Radius in \u212B within which the volume is shown.' }),
detailLevel,
selectionDetailLevel: selectionDetailLevel,
selectionDetailLevel: { ...detailLevel, label: 'Selection Detail' },
isRelative: isRelativeParam,
isUnbounded: isUnboundedParam,
}, { description: 'Box around focused element.' }),
}, { options: VolumeStreaming.ViewTypeOptions, description: 'Controls what of the volume is displayed. "Off" hides the volume alltogether. "Bounded box" shows the volume inside the given box. "Around Focus" shows the volume around the element/atom last interacted with. "Around Camera" shows the volume around the point the camera is targeting. "Whole Structure" shows the volume for the whole structure.' })
// 'auto': PD.Group({ }), // TODO based on camera distance/active selection/whatever, show whole structure or slice.
}, { options: VolumeStreaming.ViewTypeOptions, description: 'Controls what of the volume is displayed. "Off" hides the volume alltogether. "Bounded box" shows the volume inside the given box. "Around Focus" shows the volume around the element/atom last interacted with. "Whole Structure" shows the volume for the whole structure.' })
};
const options = {
entry: params.entry.name,
@@ -319,7 +299,6 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
bottomLeft: (params.entry.params.view.params as any).bottomLeft,
topRight: (params.entry.params.view.params as any).topRight,
selectionDetailLevel: (params.entry.params.view.params as any).selectionDetailLevel,
dynamicDetailLevel: (params.entry.params.view.params as any).dynamicDetailLevel,
isRelative,
isUnbounded
}

View File

@@ -141,13 +141,11 @@ class FullSettings extends PluginUIComponent {
this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate());
this.subscribe(this.plugin.layout.events.updated, () => this.forceUpdate());
if (this.plugin.canvas3d) {
this.subscribe(this.plugin.canvas3d.camera.stateChanged, state => {
if (state.radiusMax !== undefined || state.radius !== undefined) {
this.forceUpdate();
}
});
}
this.subscribe(this.plugin.canvas3d!.camera.stateChanged, state => {
if (state.radiusMax !== undefined || state.radius !== undefined) {
this.forceUpdate();
}
});
}
render() {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -288,12 +288,7 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
for (const loci of this.lociArray) {
this.plugin.managers.interactivity.lociHighlights.highlight({ loci }, false);
}
const reprLocis = this.props.cell.obj?.data.repr.getAllLoci();
if (reprLocis) {
for (const loci of reprLocis) {
this.plugin.managers.interactivity.lociHighlights.highlight({ loci }, false);
}
}
this.plugin.managers.interactivity.lociHighlights.highlight({ loci: this.props.cell.obj?.data.repr.getLoci()! }, false);
};
clearHighlight = () => {

View File

@@ -59,7 +59,7 @@ export class ViewportCanvas extends PluginUIComponent<ViewportCanvasParams, View
return <div className='msp-no-webgl'>
<div>
<p><b>WebGL does not seem to be available.</b></p>
<p>This can be caused by an outdated browser, graphics card driver issue, or bad weather. Sometimes, just restarting the browser helps. Also, make sure hardware acceleration is enabled in your browser.</p>
<p>This can be caused by an outdated browser, graphics card driver issue, or bad weather. Sometimes, just restarting the browser helps.</p>
<p>For a list of supported browsers, refer to <a href='http://caniuse.com/#feat=webgl' target='_blank'>http://caniuse.com/#feat=webgl</a>.</p>
</div>
</div>;

View File

@@ -7,15 +7,14 @@
import * as React from 'react';
import { Binding } from '../../mol-util/binding';
import { PluginUIComponent } from '../base';
import { StateTransformer, StateSelection, State } from '../../mol-state';
import { StateTransformer, StateSelection } from '../../mol-state';
import { SelectLoci } from '../../mol-plugin/behavior/dynamic/representation';
import { FocusLoci } from '../../mol-plugin/behavior/dynamic/representation';
import { Icon, ArrowDropDownSvg, ArrowRightSvg, CameraSvg } from '../controls/icons';
import { Button } from '../controls/common';
import { memoizeLatest } from '../../mol-util/memoize';
function getBindingsList(bindings: { [k: string]: Binding }) {
return Object.keys(bindings).map(k => [k, bindings[k]] as [string, Binding]).filter(b => Binding.isBinding(b[1]));
return Object.keys(bindings).map(k => [k, bindings[k]] as [string, Binding]);
}
export class BindingsHelp extends React.PureComponent<{ bindings: { [k: string]: Binding } }> {
@@ -78,30 +77,19 @@ export class ViewportHelpContent extends PluginUIComponent<{ selectOnly?: boolea
this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate());
}
getInteractionBindings = memoizeLatest((cells: State.Cells) => {
let interactionBindings: { [k: string]: Binding } | undefined = void 0;
cells.forEach(c => {
const params = c.params?.values;
if (params?.bindings && Object.keys(params.bindings).length > 0) {
if (!interactionBindings) interactionBindings = { };
Object.assign(interactionBindings, params.bindings);
}
});
return interactionBindings;
});
render() {
const interactionBindings = this.getInteractionBindings(this.plugin.state.behaviors.cells);
const interactionBindings: { [k: string]: Binding } = {};
this.plugin.spec.behaviors.forEach(b => {
const { bindings } = b.defaultParams;
if (bindings) Object.assign(interactionBindings, bindings);
});
return <>
{(!this.props.selectOnly && this.plugin.canvas3d) && <HelpGroup key='trackball' header='Moving in 3D'>
<BindingsHelp bindings={this.plugin.canvas3d.props.trackball.bindings} />
</HelpGroup>}
{!!interactionBindings && <HelpGroup key='interactions' header='Mouse Controls'>
<HelpGroup key='interactions' header='Mouse Controls'>
<BindingsHelp bindings={interactionBindings} />
</HelpGroup>}
</HelpGroup>
</>;
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -96,21 +96,18 @@ export class DownloadScreenshotControls extends PluginUIComponent<{ close: () =>
}
function ScreenshotParams({ plugin, isDisabled }: { plugin: PluginContext, isDisabled: boolean }) {
const helper = plugin.helpers.viewportScreenshot;
const values = useBehavior(helper?.behaviors.values);
if (!helper) return null;
const helper = plugin.helpers.viewportScreenshot!;
const values = useBehavior(helper.behaviors.values);
return <ParameterControls params={helper.params} values={values} onChangeValues={v => helper.behaviors.values.next(v)} isDisabled={isDisabled} />;
}
function CropControls({ plugin }: { plugin: PluginContext }) {
const helper = plugin.helpers.viewportScreenshot;
const cropParams = useBehavior(helper?.behaviors.cropParams);
const cropParams = useBehavior(helper?.behaviors.cropParams!);
useBehavior(helper?.behaviors.relativeCrop);
if (!helper || !cropParams) return null;
if (!helper) return null;
return <div style={{ width: '100%', height: '24px', marginTop: '8px' }}>
<ToggleButton icon={CropOrginalSvg} title='Auto-crop' inline isSelected={cropParams.auto}

View File

@@ -22,8 +22,6 @@ import { ViewportHelpContent } from './help';
export class SimpleSettingsControl extends PluginUIComponent {
componentDidMount() {
if (!this.plugin.canvas3d) return;
this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate());
this.subscribe(this.plugin.canvas3d!.camera.stateChanged, state => {

View File

@@ -1,8 +1,7 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Adam Midlik <midlik@gmail.com>
*/
import { PluginStateTransform, PluginStateObject } from '../../mol-plugin-state/objects';
@@ -40,7 +39,7 @@ namespace PluginBehavior {
'misc': 'Miscellaneous'
};
export interface CreateParams<P extends {}> {
export interface CreateParams<P> {
name: string,
category: keyof typeof Categories,
ctor: Ctor<P>,
@@ -73,7 +72,7 @@ namespace PluginBehavior {
return categoryMap.get(t.id)!;
}
export function create<P extends {}>(params: CreateParams<P>) {
export function create<P>(params: CreateParams<P>) {
const t = PluginStateTransform.CreateBuiltIn<Category, Behavior, P>({
name: params.name,
display: params.display,
@@ -113,7 +112,7 @@ namespace PluginBehavior {
};
}
export abstract class Handler<P extends {} = {}> implements PluginBehavior<P> {
export abstract class Handler<P = { }> implements PluginBehavior<P> {
private subs: PluginCommand.Subscription[] = [];
protected subscribeCommand<T>(cmd: PluginCommand<T>, action: PluginCommand.Action<T>) {
this.subs.push(cmd.subscribe(this.ctx, action));
@@ -145,18 +144,8 @@ namespace PluginBehavior {
protected subscribeCommand<T>(cmd: PluginCommand<T>, action: PluginCommand.Action<T>) {
this.subs.push(cmd.subscribe(this.plugin, action));
}
protected subscribeObservable<T>(o: Observable<T>, action: (v: T) => void): PluginCommand.Subscription {
const sub = o.subscribe(action);
this.subs.push(sub);
return {
unsubscribe: () => {
const idx = this.subs.indexOf(sub);
if (idx >= 0) {
this.subs.splice(idx, 1);
sub.unsubscribe();
}
}
};
protected subscribeObservable<T>(o: Observable<T>, action: (v: T) => void) {
this.subs.push(o.subscribe(action));
}
dispose(): void {
for (const s of this.subs) s.unsubscribe();

View File

@@ -1,9 +1,8 @@
/**
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Adam Midlik <midlik@gmail.com>
*/
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
@@ -25,10 +24,6 @@ import { PluginContext } from '../../../context';
import { EmptyLoci, Loci, isEmptyLoci } from '../../../../mol-model/loci';
import { Asset } from '../../../../mol-util/assets';
import { GlobalModelTransformInfo } from '../../../../mol-model/structure/model/properties/global-transform';
import { distinctUntilChanged, filter, map, Observable, throttleTime } from 'rxjs';
import { Camera } from '../../../../mol-canvas3d/camera';
import { PluginCommand } from '../../../command';
import { SingleAsyncQueue } from '../../../../mol-util/single-async-queue';
export class VolumeStreaming extends PluginStateObject.CreateBehavior<VolumeStreaming.Behavior>({ name: 'Volume Streaming' }) { }
@@ -58,7 +53,7 @@ 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(options: { data?: VolumeServerInfo.Data, defaultView?: ViewTypes, channelParams?: DefaultChannelParams } = {}) {
export function createParams(options: { data?: VolumeServerInfo.Data, defaultView?: ViewTypes, channelParams?: DefaultChannelParams } = { }) {
const { data, defaultView, channelParams } = options;
const map = new Map<string, VolumeServerInfo.EntryData>();
if (data) data.entries.forEach(d => map.set(d.dataId, d));
@@ -73,7 +68,7 @@ export namespace VolumeStreaming {
export type EntryParams = PD.Values<EntryParamDefinition>
export function createEntryParams(options: { entryData?: VolumeServerInfo.EntryData, defaultView?: ViewTypes, structure?: Structure, channelParams?: DefaultChannelParams }) {
const { entryData, defaultView, structure, channelParams = {} } = options;
const { entryData, defaultView, structure, channelParams = { } } = options;
// fake the info
const info = entryData || { kind: 'em', header: { sampling: [fakeSampling], availablePrecisions: [{ precision: 0, maxVoxels: 0 }] }, emDefaultContourLevel: Volume.IsoValue.relative(0) };
@@ -91,24 +86,19 @@ export namespace VolumeStreaming {
bottomLeft: PD.Vec3(Vec3.create(0, 0, 0), {}, { isHidden: true }),
topRight: PD.Vec3(Vec3.create(0, 0, 0), {}, { isHidden: true }),
}, { description: 'Box around focused element.', isFlat: true }),
'camera-target': PD.Group({
radius: PD.Numeric(0.5, { min: 0, max: 1, step: 0.05 }, { description: 'Radius within which the volume is shown (relative to the field of view).' }),
// Minimal detail level for the inside of the zoomed region (real detail can be higher, depending on the region size)
dynamicDetailLevel: createDetailParams(info.header.availablePrecisions, 0, { label: 'Dynamic Detail' }),
bottomLeft: PD.Vec3(Vec3.create(0, 0, 0), {}, { isHidden: true }),
topRight: PD.Vec3(Vec3.create(0, 0, 0), {}, { isHidden: true }),
}, { description: 'Box around camera target.', isFlat: true }),
'cell': PD.Group<{}>({}),
// Show selection-box if available and cell otherwise.
'auto': PD.Group({
radius: PD.Numeric(5, { min: 0, max: 50, step: 0.5 }, { description: 'Radius in \u212B within which the volume is shown.' }),
selectionDetailLevel: createDetailParams(info.header.availablePrecisions, 6, { label: 'Selection Detail' }),
selectionDetailLevel: PD.Select<number>(Math.min(6, info.header.availablePrecisions.length - 1),
info.header.availablePrecisions.map((p, i) => [i, `${i + 1} [ ${Math.pow(p.maxVoxels, 1 / 3) | 0}^3 cells ]`] as [number, string]), { label: 'Selection Detail', description: 'Determines the maximum number of voxels. Depending on the size of the volume options are in the range from 0 (0.52M voxels) to 6 (25.17M voxels).' }),
isSelection: PD.Boolean(false, { isHidden: true }),
bottomLeft: PD.Vec3(box.min, {}, { isHidden: true }),
topRight: PD.Vec3(box.max, {}, { isHidden: true }),
}, { description: 'Box around focused element.', isFlat: true })
}, { options: ViewTypeOptions, description: 'Controls what of the volume is displayed. "Off" hides the volume alltogether. "Bounded box" shows the volume inside the given box. "Around Interaction" shows the volume around the focused element/atom. "Whole Structure" shows the volume for the whole structure.' }),
detailLevel: createDetailParams(info.header.availablePrecisions, 3),
detailLevel: PD.Select<number>(Math.min(3, info.header.availablePrecisions.length - 1),
info.header.availablePrecisions.map((p, i) => [i, `${i + 1} [ ${Math.pow(p.maxVoxels, 1 / 3) | 0}^3 cells ]`] as [number, string]), { description: 'Determines the maximum number of voxels. Depending on the size of the volume options are in the range from 0 (0.52M voxels) to 6 (25.17M voxels).' }),
channels: info.kind === 'em'
? PD.Group({
'em': channelParam('EM', Color(0x638F8F), info.emDefaultContourLevel || Volume.IsoValue.relative(1), info.header.sampling[0].valuesInfo[0], channelParams['em'])
@@ -121,40 +111,13 @@ export namespace VolumeStreaming {
};
}
function createDetailParams(availablePrecisions: VolumeServerHeader.DetailLevel[], preferredPrecision: number, info?: PD.Info) {
return PD.Select<number>(Math.min(preferredPrecision, availablePrecisions.length - 1),
availablePrecisions.map((p, i) => [i, `${i + 1} [ ${Math.pow(p.maxVoxels, 1 / 3) | 0}^3 cells ]`] as [number, string]),
{
description: 'Determines the maximum number of voxels. Depending on the size of the volume options are in the range from 1 (0.52M voxels) to 7 (25.17M voxels).',
...info
}
);
}
export const ViewTypeOptions = [['off', 'Off'], ['box', 'Bounded Box'], ['selection-box', 'Around Focus'], ['cell', 'Whole Structure'], ['auto', 'Auto']] as [ViewTypes, string][];
export function copyParams(origParams: Params): Params {
return {
entry: {
name: origParams.entry.name,
params: {
detailLevel: origParams.entry.params.detailLevel,
channels: origParams.entry.params.channels,
view: {
name: origParams.entry.params.view.name,
params: { ...origParams.entry.params.view.params } as any,
}
}
}
};
}
export const ViewTypeOptions = [['off', 'Off'], ['box', 'Bounded Box'], ['selection-box', 'Around Focus'], ['camera-target', 'Around Camera'], ['cell', 'Whole Structure'], ['auto', 'Auto']] as [ViewTypes, string][];
export type ViewTypes = 'off' | 'box' | 'selection-box' | 'camera-target' | 'cell' | 'auto'
export type ViewTypes = 'off' | 'box' | 'selection-box' | 'cell' | 'auto'
export type ParamDefinition = ReturnType<typeof createParams>
export type Params = PD.Values<ParamDefinition>
type ChannelsInfo = { [name in ChannelType]?: { isoValue: Volume.IsoValue, color: Color, wireframe: boolean, opacity: number } }
type ChannelsData = { [name in 'EM' | '2FO-FC' | 'FO-FC']?: Volume }
@@ -177,14 +140,6 @@ export namespace VolumeStreaming {
private lastLoci: StructureElement.Loci | EmptyLoci = EmptyLoci;
private ref: string = '';
public infoMap: Map<string, VolumeServerInfo.EntryData>;
private updateQueue: SingleAsyncQueue;
private cameraTargetObservable = this.plugin.canvas3d!.didDraw!.pipe(
throttleTime(500, undefined, { 'leading': true, 'trailing': true }),
map(() => this.plugin.canvas3d?.camera.getSnapshot()),
distinctUntilChanged((a, b) => this.isCameraTargetSame(a, b)),
filter(a => a !== undefined),
) as Observable<Camera.Snapshot>;
private cameraTargetSubscription?: PluginCommand.Subscription = undefined;
channels: Channels = {};
@@ -208,9 +163,6 @@ export namespace VolumeStreaming {
if (this.params.entry.params.view.name === 'auto' && this.params.entry.params.view.params.isSelection) {
detail = this.params.entry.params.view.params.selectionDetailLevel;
}
if (this.params.entry.params.view.name === 'camera-target' && box) {
detail = this.decideDetail(box, this.params.entry.params.view.params.dynamicDetailLevel);
}
url += `?detail=${detail}`;
@@ -249,21 +201,58 @@ export namespace VolumeStreaming {
return ret;
}
private async updateParams(box: Box3D | undefined, autoIsSelection: boolean = false) {
const newParams = copyParams(this.params);
const viewType = newParams.entry.params.view.name;
if (viewType !== 'off' && viewType !== 'cell') {
newParams.entry.params.view.params.bottomLeft = box?.min || Vec3.zero();
newParams.entry.params.view.params.topRight = box?.max || Vec3.zero();
}
if (viewType === 'auto') {
newParams.entry.params.view.params.isSelection = autoIsSelection;
}
private updateSelectionBoxParams(box: Box3D) {
if (this.params.entry.params.view.name !== 'selection-box') return;
const state = this.plugin.state.data;
const newParams: Params = {
...this.params,
entry: {
name: this.params.entry.name,
params: {
...this.params.entry.params,
view: {
name: 'selection-box' as const,
params: {
radius: this.params.entry.params.view.params.radius,
bottomLeft: box.min,
topRight: box.max
}
}
}
}
};
const update = state.build().to(this.ref).update(newParams);
await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotUpdateCurrent: true } });
PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotUpdateCurrent: true } });
}
private updateAutoParams(box: Box3D | undefined, isSelection: boolean) {
if (this.params.entry.params.view.name !== 'auto') return;
const state = this.plugin.state.data;
const newParams: Params = {
...this.params,
entry: {
name: this.params.entry.name,
params: {
...this.params.entry.params,
view: {
name: 'auto' as const,
params: {
radius: this.params.entry.params.view.params.radius,
selectionDetailLevel: this.params.entry.params.view.params.selectionDetailLevel,
isSelection,
bottomLeft: box?.min || Vec3.zero(),
topRight: box?.max || Vec3.zero()
}
}
}
}
};
const update = state.build().to(this.ref).update(newParams);
PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotUpdateCurrent: true } });
}
private getStructureRoot() {
@@ -314,18 +303,6 @@ export namespace VolumeStreaming {
}
}
private isCameraTargetSame(a?: Camera.Snapshot, b?: Camera.Snapshot): boolean {
if (!a || !b) return false;
const targetSame = Vec3.equals(a.target, b.target);
const sqDistA = Vec3.squaredDistance(a.target, a.position);
const sqDistB = Vec3.squaredDistance(b.target, b.position);
const distanceSame = Math.abs(sqDistA - sqDistB) / sqDistA < 1e-3;
return targetSame && distanceSame;
}
private cameraTargetDistance(snapshot: Camera.Snapshot): number {
return Vec3.distance(snapshot.target, snapshot.position);
}
private _invTransform: Mat4 = Mat4();
private getBoxFromLoci(loci: StructureElement.Loci | EmptyLoci): Box3D {
if (Loci.isEmpty(loci) || isEmptyLoci(loci)) {
@@ -351,82 +328,39 @@ export namespace VolumeStreaming {
}
private updateAuto(loci: StructureElement.Loci | EmptyLoci) {
this.updateQueue.enqueue(async () => {
this.lastLoci = loci;
if (isEmptyLoci(loci)) {
await this.updateParams(this.info.kind === 'x-ray' ? this.data.structure.boundary.box : void 0, false);
} else {
await this.updateParams(this.getBoxFromLoci(loci), true);
}
});
// if (Loci.areEqual(this.lastLoci, loci)) {
// this.lastLoci = EmptyLoci;
// this.updateSelectionBoxParams(Box3D.empty());
// return;
// }
this.lastLoci = loci;
if (isEmptyLoci(loci)) {
this.updateAutoParams(this.info.kind === 'x-ray' ? this.data.structure.boundary.box : void 0, false);
return;
}
const box = this.getBoxFromLoci(loci);
this.updateAutoParams(box, true);
}
private updateSelectionBox(loci: StructureElement.Loci | EmptyLoci) {
this.updateQueue.enqueue(async () => {
if (Loci.areEqual(this.lastLoci, loci)) {
this.lastLoci = EmptyLoci;
} else {
this.lastLoci = loci;
}
const box = this.getBoxFromLoci(this.lastLoci);
await this.updateParams(box);
});
}
private updateCameraTarget(snapshot: Camera.Snapshot) {
this.updateQueue.enqueue(async () => {
const origManualReset = this.plugin.canvas3d?.props.camera.manualReset;
try {
if (!origManualReset) this.plugin.canvas3d?.setProps({ camera: { manualReset: true } });
const box = this.boxFromCameraTarget(snapshot, true);
await this.updateParams(box);
} finally {
if (!origManualReset) this.plugin.canvas3d?.setProps({ camera: { manualReset: origManualReset } });
}
});
}
private boxFromCameraTarget(snapshot: Camera.Snapshot, boundByBoundarySize: boolean): Box3D {
const target = snapshot.target;
const distance = this.cameraTargetDistance(snapshot);
const top = Math.tan(0.5 * snapshot.fov) * distance;
let radius = top;
const viewport = this.plugin.canvas3d?.camera.viewport;
if (viewport && viewport.width > viewport.height) {
radius *= viewport.width / viewport.height;
if (Loci.areEqual(this.lastLoci, loci)) {
this.lastLoci = EmptyLoci;
this.updateSelectionBoxParams(Box3D());
return;
}
const relativeRadius = this.params.entry.params.view.name === 'camera-target' ? this.params.entry.params.view.params.radius : 0.5;
radius *= relativeRadius;
let radiusX, radiusY, radiusZ;
if (boundByBoundarySize) {
const bBoxSize = Vec3.zero();
Box3D.size(bBoxSize, this.data.structure.boundary.box);
radiusX = Math.min(radius, 0.5 * bBoxSize[0]);
radiusY = Math.min(radius, 0.5 * bBoxSize[1]);
radiusZ = Math.min(radius, 0.5 * bBoxSize[2]);
} else {
radiusX = radiusY = radiusZ = radius;
}
return Box3D.create(
Vec3.create(target[0] - radiusX, target[1] - radiusY, target[2] - radiusZ),
Vec3.create(target[0] + radiusX, target[1] + radiusY, target[2] + radiusZ)
);
}
private decideDetail(box: Box3D, baseDetail: number): number {
const cellVolume = this.info.kind === 'x-ray'
? Box3D.volume(this.data.structure.boundary.box)
: this.info.header.spacegroup.size.reduce((a, b) => a * b, 1);
const boxVolume = Box3D.volume(box);
let ratio = boxVolume / cellVolume;
const maxDetail = this.info.header.availablePrecisions.length - 1;
let detail = baseDetail;
while (ratio <= 0.5 && detail < maxDetail) {
ratio *= 2;
detail += 1;
this.lastLoci = loci;
if (isEmptyLoci(loci)) {
this.updateSelectionBoxParams(Box3D());
return;
}
// console.log(`Decided dynamic detail: ${detail}, (base detail: ${baseDetail}, box/cell volume ratio: ${boxVolume / cellVolume})`);
return detail;
const box = this.getBoxFromLoci(loci);
this.updateSelectionBoxParams(box);
}
async update(params: Params) {
@@ -435,11 +369,6 @@ export namespace VolumeStreaming {
this.params = params;
let box: Box3D | undefined = void 0, emptyData = false;
if (params.entry.params.view.name !== 'camera-target' && this.cameraTargetSubscription) {
this.cameraTargetSubscription.unsubscribe();
this.cameraTargetSubscription = undefined;
}
switch (params.entry.params.view.name) {
case 'off':
emptyData = true;
@@ -459,12 +388,6 @@ export namespace VolumeStreaming {
Box3D.expand(box, box, Vec3.create(r, r, r));
break;
}
case 'camera-target':
if (!this.cameraTargetSubscription) {
this.cameraTargetSubscription = this.subscribeObservable(this.cameraTargetObservable, (e) => this.updateCameraTarget(e));
}
box = this.boxFromCameraTarget(this.plugin.canvas3d!.camera.getSnapshot(), true);
break;
case 'cell':
box = this.info.kind === 'x-ray'
? this.data.structure.boundary.box
@@ -516,7 +439,6 @@ export namespace VolumeStreaming {
getDescription() {
if (this.params.entry.params.view.name === 'selection-box') return 'Selection';
if (this.params.entry.params.view.name === 'camera-target') return 'Camera';
if (this.params.entry.params.view.name === 'box') return 'Static Box';
if (this.params.entry.params.view.name === 'cell') return 'Cell';
return '';
@@ -527,7 +449,6 @@ export namespace VolumeStreaming {
this.infoMap = new Map<string, VolumeServerInfo.EntryData>();
this.data.entries.forEach(info => this.infoMap.set(info.dataId, info));
this.updateQueue = new SingleAsyncQueue();
}
}
}
}

View File

@@ -1,9 +1,8 @@
/**
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Adam Midlik <midlik@gmail.com>
*/
import { PluginStateObject as SO, PluginStateTransform } from '../../../../mol-plugin-state/objects';
@@ -220,7 +219,6 @@ const CreateVolumeStreamingBehavior = PluginStateTransform.BuiltIn({
canAutoUpdate: ({ oldParams, newParams }) => {
return oldParams.entry.params.view === newParams.entry.params.view
|| newParams.entry.params.view.name === 'selection-box'
|| newParams.entry.params.view.name === 'camera-target'
|| newParams.entry.params.view.name === 'off';
},
apply: ({ a, params }, plugin: PluginContext) => Task.create('Volume streaming', async _ => {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -119,9 +119,7 @@ export function Highlight(ctx: PluginContext) {
ctx.managers.interactivity.lociHighlights.highlight({ loci: Structure.Loci(cell.obj.data) }, false);
} else if (cell && SO.isRepresentation3D(cell.obj)) {
const { repr } = cell.obj.data;
for (const loci of repr.getAllLoci()) {
ctx.managers.interactivity.lociHighlights.highlight({ loci, repr }, false);
}
ctx.managers.interactivity.lociHighlights.highlight({ loci: repr.getLoci(), repr }, false);
} else if (SO.Molecule.Structure.Selections.is(cell.obj)) {
for (const entry of cell.obj.data) {
ctx.managers.interactivity.lociHighlights.highlight({ loci: entry.loci }, false);

View File

@@ -31,12 +31,10 @@ export const PluginConfig = {
PixelScale: item('plugin-config.pixel-scale', 1),
PickScale: item('plugin-config.pick-scale', 0.25),
PickPadding: item('plugin-config.pick-padding', 3),
EnableWboit: item('plugin-config.enable-wboit', true),
EnableDpoit: item('plugin-config.enable-dpoit', false),
EnableWboit: item('plugin-config.enable-wboit', PluginFeatureDetection.wboit),
// as of Oct 1 2021, WebGL 2 doesn't work on iOS 15.
// TODO: check back in a few weeks to see if it was fixed
PreferWebGl1: item('plugin-config.prefer-webgl1', PluginFeatureDetection.preferWebGl1),
AllowMajorPerformanceCaveat: item('plugin-config.allow-major-performance-caveat', false),
},
State: {
DefaultServer: item('plugin-state.server', 'https://webchem.ncbr.muni.cz/molstar-state'),
@@ -94,4 +92,4 @@ export class PluginConfigManager {
if (!initial) return;
initial.forEach(([k, v]) => this._config.set(k, v));
}
}
}

View File

@@ -200,10 +200,8 @@ export class PluginContext {
const pickScale = this.config.get(PluginConfig.General.PickScale) || 0.25;
const pickPadding = this.config.get(PluginConfig.General.PickPadding) ?? 1;
const enableWboit = this.config.get(PluginConfig.General.EnableWboit) || false;
const enableDpoit = this.config.get(PluginConfig.General.EnableDpoit) || false;
const preferWebGl1 = this.config.get(PluginConfig.General.PreferWebGl1) || false;
const failIfMajorPerformanceCaveat = !(this.config.get(PluginConfig.General.AllowMajorPerformanceCaveat) ?? false);
(this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, this.managers.asset, { antialias, preserveDrawingBuffer, pixelScale, pickScale, pickPadding, enableWboit, enableDpoit, preferWebGl1, failIfMajorPerformanceCaveat });
(this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, this.managers.asset, { antialias, preserveDrawingBuffer, pixelScale, pickScale, pickPadding, enableWboit, preferWebGl1 });
}
(this.canvas3d as Canvas3D) = Canvas3D.create(this.canvas3dContext!);
this.canvas3dInit.next(true);

View File

@@ -29,4 +29,11 @@ export const PluginFeatureDetection = {
const isTouchScreen = navigator.maxTouchPoints >= 4; // true for iOS 13 (and hopefully beyond)
return !(window as any).MSStream && (isIOS || (isAppleDevice && isTouchScreen));
},
get wboit() {
return true; // for testing
// if (typeof navigator === 'undefined' || typeof window === 'undefined') return true;
// // disable Wboit in Safari 15
// return !/Version\/15.\d Safari/.test(navigator.userAgent);
}
};

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -154,8 +154,8 @@ interface Representation<D, P extends PD.Params = {}, S extends Representation.S
createOrUpdate: (props?: Partial<PD.Values<P>>, data?: D) => Task<void>
setState: (state: Partial<S>) => void
setTheme: (theme: Theme) => void
getLoci: (pickingId: PickingId) => ModelLoci
getAllLoci: () => ModelLoci[]
/** If no pickingId is given, returns a Loci for the whole representation */
getLoci: (pickingId?: PickingId) => ModelLoci
mark: (loci: ModelLoci, action: MarkerAction) => boolean
destroy: () => void
}
@@ -227,7 +227,6 @@ namespace Representation {
setState: () => {},
setTheme: () => {},
getLoci: () => EmptyLoci,
getAllLoci: () => [],
mark: () => false,
destroy: () => {}
};
@@ -328,7 +327,7 @@ namespace Representation {
},
get state() { return currentState; },
get theme() { return currentTheme; },
getLoci: (pickingId: PickingId) => {
getLoci: (pickingId?: PickingId) => {
const { visuals } = currentProps;
for (let i = 0, il = reprList.length; i < il; ++i) {
if (!visuals || visuals.includes(reprMap[i])) {
@@ -338,16 +337,6 @@ namespace Representation {
}
return EmptyLoci;
},
getAllLoci: () => {
const loci: ModelLoci[] = [];
const { visuals } = currentProps;
for (let i = 0, il = reprList.length; i < il; ++i) {
if (!visuals || visuals.includes(reprMap[i])) {
loci.push(...reprList[i].getAllLoci());
}
}
return loci;
},
mark: (loci: ModelLoci, action: MarkerAction) => {
let marked = false;
for (let i = 0, il = reprList.length; i < il; ++i) {
@@ -410,10 +399,6 @@ namespace Representation {
// TODO
return EmptyLoci;
},
getAllLoci: () => {
// TODO
return [];
},
mark: (loci: ModelLoci, action: MarkerAction) => {
// TODO
return false;

View File

@@ -213,16 +213,14 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
get geometryVersion() { return geometryVersion; },
updated,
createOrUpdate,
getLoci(pickingId: PickingId) {
getLoci(pickingId?: PickingId) {
if (pickingId === undefined) return Shape.Loci(_shape);
const { objectId, groupId, instanceId } = pickingId;
if (_renderObject && _renderObject.id === objectId) {
return ShapeGroup.Loci(_shape, [{ ids: OrderedSet.ofSingleton(groupId), instance: instanceId }]);
}
return EmptyLoci;
},
getAllLoci() {
return [Shape.Loci(_shape)];
},
mark(loci: Loci, action: MarkerAction) {
if (!MarkerActions.is(_state.markerActions, action)) return false;
if (ShapeGroup.isLoci(loci) || Shape.isLoci(loci)) {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -72,14 +72,11 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
});
}
function getLoci(pickingId: PickingId) {
function getLoci(pickingId?: PickingId) {
if (pickingId === undefined) return Structure.Loci(_structure.target);
return visual ? visual.getLoci(pickingId) : EmptyLoci;
}
function getAllLoci() {
return [Structure.Loci(_structure.target)];
}
function mark(loci: Loci, action: MarkerAction) {
if (!_structure) return false;
if (!MarkerActions.is(_state.markerActions, action)) return false;
@@ -160,7 +157,6 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
setState,
setTheme,
getLoci,
getAllLoci,
mark,
destroy
};

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -185,7 +185,8 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
});
}
function getLoci(pickingId: PickingId) {
function getLoci(pickingId?: PickingId) {
if (pickingId === undefined) return Structure.Loci(_structure.target);
let loci: Loci = EmptyLoci;
visuals.forEach(({ visual }) => {
const _loci = visual.getLoci(pickingId);
@@ -194,10 +195,6 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
return loci;
}
function getAllLoci() {
return [Structure.Loci(_structure.target)];
}
function mark(loci: Loci, action: MarkerAction) {
if (!_structure) return false;
if (!MarkerActions.is(_state.markerActions, action)) return false;
@@ -305,7 +302,6 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
setState,
setTheme,
getLoci,
getAllLoci,
mark,
destroy
};

View File

@@ -358,12 +358,10 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx:
createOrUpdate,
setState,
setTheme,
getLoci: (pickingId: PickingId): Loci => {
getLoci: (pickingId?: PickingId): Loci => {
if (pickingId === undefined) return getLoci(_volume, _props);
return visual ? visual.getLoci(pickingId) : EmptyLoci;
},
getAllLoci: (): Loci[] => {
return [getLoci(_volume, _props)];
},
mark,
destroy
};

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2022 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>
@@ -333,7 +333,6 @@ const bondProperty = {
flags: bondProp(Types.BondFlags),
order: bondProp(Type.Num),
key: bondProp(Type.Num),
length: bondProp(Type.Num),
atomA: bondProp(Types.ElementReference),
atomB: bondProp(Types.ElementReference)
@@ -357,5 +356,5 @@ export const structureQuery = {
combinator,
atomSet,
atomProperty,
bondProperty
bondProperty: bondProperty
};

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2022 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>
@@ -211,21 +211,21 @@ const symbols = [
// ============= FILTERS ================
D(MolScript.structureQuery.filter.pick, (ctx, xs) => Queries.filters.pick(xs[0] as any, xs['test'])(ctx)),
D(MolScript.structureQuery.filter.first, (ctx, xs) => Queries.filters.first(xs[0] as any)(ctx)),
D(MolScript.structureQuery.filter.withSameAtomProperties, (ctx, xs) => Queries.filters.withSameAtomProperties(xs[0] as any, xs['source'] as any, xs['property'])(ctx)),
D(MolScript.structureQuery.filter.withSameAtomProperties, (ctx, xs) => Queries.filters.withSameAtomProperties(xs[0] as any, xs['source'] as any, xs['property'] as any)(ctx)),
D(MolScript.structureQuery.filter.intersectedBy, (ctx, xs) => Queries.filters.areIntersectedBy(xs[0] as any, xs['by'] as any)(ctx)),
D(MolScript.structureQuery.filter.within, (ctx, xs) => Queries.filters.within({
query: xs[0] as any,
target: xs['target'] as any,
minRadius: xs['min-radius']?.(ctx) as any,
maxRadius: xs['max-radius']?.(ctx) as any,
minRadius: xs['min-radius'] as any,
maxRadius: xs['max-radius'] as any,
elementRadius: xs['atom-radius'] as any,
invert: xs['invert']?.(ctx) as any
invert: xs['invert'] as any
})(ctx)),
D(MolScript.structureQuery.filter.isConnectedTo, (ctx, xs) => Queries.filters.isConnectedTo({
query: xs[0] as any,
target: xs['target'] as any,
disjunct: xs['disjunct']?.(ctx) as any,
invert: xs['invert']?.(ctx) as any,
disjunct: xs['disjunct'] as any,
invert: xs['invert'] as any,
bondTest: xs['bond-test']
})(ctx)),
@@ -248,9 +248,6 @@ const symbols = [
D(MolScript.structureQuery.generator.rings, function structureQuery_generator_rings(ctx, xs) {
return Queries.generators.rings(xs?.['fingerprint']?.(ctx) as any, xs?.['only-aromatic']?.(ctx))(ctx);
}),
D(MolScript.structureQuery.generator.queryInSelection, function structureQuery_generator_queryInSelection(ctx, xs) {
return Queries.generators.querySelection(xs[0] as any, xs['query'] as any, xs['in-complement']?.(ctx) as any)(ctx);
}),
// ============= MODIFIERS ================
@@ -281,7 +278,6 @@ const symbols = [
fixedPoint: xs['fixed-point']?.(ctx) ?? false
})(ctx);
}),
D(MolScript.structureQuery.modifier.intersectBy, function structureQuery_modifier_intersectBy(ctx, xs) { return Queries.modifiers.intersectBy(xs[0] as any, xs['by'] as any)(ctx); }),
// ============= COMBINATORS ================
@@ -357,27 +353,9 @@ const symbols = [
D(MolScript.structureQuery.atomProperty.macromolecular.secondaryStructureFlags, atomProp(StructureProperties.residue.secondary_structure_type)),
D(MolScript.structureQuery.atomProperty.macromolecular.chemCompType, atomProp(StructureProperties.residue.chem_comp_type)),
// ============= ATOM SET ================
D(MolScript.structureQuery.atomSet.atomCount,
function structureQuery_atomset_atomCount(ctx, xs) {
return Queries.atomset.atomCount(ctx);
}),
D(MolScript.structureQuery.atomSet.countQuery,
function structureQuery_atomset_countQuery(ctx, xs) {
return Queries.atomset.countQuery(xs[0] as any)(ctx);
}),
D(MolScript.structureQuery.atomSet.propertySet,
function structureQuery_atomset_propertySet(ctx, xs) {
return Queries.atomset.propertySet(xs[0] as any)(ctx);
}),
// ============= BOND PROPERTIES ================
D(MolScript.structureQuery.bondProperty.order, (ctx, xs) => ctx.atomicBond.order),
D(MolScript.structureQuery.bondProperty.flags, (ctx, xs) => ctx.atomicBond.type),
D(MolScript.structureQuery.bondProperty.key, (ctx, xs) => ctx.atomicBond.key),
D(MolScript.structureQuery.bondProperty.atomA, (ctx, xs) => ctx.atomicBond.a),
D(MolScript.structureQuery.bondProperty.atomB, (ctx, xs) => ctx.atomicBond.b),
D(MolScript.structureQuery.bondProperty.length, (ctx, xs) => ctx.atomicBond.length),
@@ -428,4 +406,4 @@ function getArray<T = any>(ctx: QueryContext, xs: any): T[] {
for (const s of symbols) {
DefaultQueryRuntimeTable.addSymbol(s);
}
})();
})();

View File

@@ -6,12 +6,10 @@
import { transpileMolScript } from './script/mol-script/symbols';
import { parseMolScript } from './language/parser';
import { parse } from './transpile';
import { Expression } from './language/expression';
import { StructureElement, QueryContext, StructureSelection, Structure, QueryFn, QueryContextOptions } from '../mol-model/structure';
import { compile } from './runtime/query/compiler';
import { MolScriptBuilder } from './language/builder';
import { assertUnreachable } from '../mol-util/type-helpers';
export { Script };
@@ -22,13 +20,7 @@ function Script(expression: string, language: Script.Language): Script {
}
namespace Script {
export const Info = {
'mol-script': 'Mol-Script',
'pymol': 'PyMOL',
'vmd': 'VMD',
'jmol': 'Jmol',
};
export type Language = keyof typeof Info;
export type Language = 'mol-script'
export function is(x: any): x is Script {
return !!x && typeof (x as Script).expression === 'string' && !!(x as Script).language;
@@ -44,13 +36,8 @@ namespace Script {
const parsed = parseMolScript(script.expression);
if (parsed.length === 0) throw new Error('No query');
return transpileMolScript(parsed[0]);
case 'pymol':
case 'jmol':
case 'vmd':
return parse(script.language, script.expression);
default:
assertUnreachable(script.language);
}
throw new Error('unsupported script language');
}
export function toQuery(script: Script): QueryFn<StructureSelection> {
@@ -69,4 +56,4 @@ namespace Script {
const query = compile<StructureSelection>(e);
return query(new QueryContext(structure, options));
}
}
}

View File

@@ -1,27 +0,0 @@
/**
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Koya Sakuma <koya.sakuma.work@gmail.com>
*
* Adapted from MolQL src/transpile.ts
*/
import { Transpiler } from './transpilers/transpiler';
import { _transpiler } from './transpilers/all';
import { Expression } from './language/expression';
import { Script } from './script';
const transpiler: {[index: string]: Transpiler} = _transpiler;
export function parse(lang: Script.Language, str: string): Expression {
try {
const query = transpiler[lang](str);
return query;
} catch (e) {
console.error(e.message);
throw e;
}
}

View File

@@ -1,26 +0,0 @@
/**
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* @author Koya Sakuma <koya.sakuma.work@gmail.com>
* Adapted from MolQL project
**/
import { Transpiler } from '../transpiler';
import { _transpiler as transpilers } from '../all';
function testTranspilerExamples(name: string, transpiler: Transpiler) {
describe(`${name} examples`, () => {
const examples = require(`../${name}/examples`).examples;
// console.log(examples);
for (const e of examples) {
it(e.name, () => {
// check if it transpiles and compiles/typechecks.
transpiler(e.value);
});
}
});
}
testTranspilerExamples('pymol', transpilers.pymol);
testTranspilerExamples('vmd', transpilers.vmd);
testTranspilerExamples('jmol', transpilers.jmol);

View File

@@ -1,115 +0,0 @@
/**
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Koya Sakuma <koya.sakuma.work@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*
* Adapted from MolQL project
*/
import * as u from './utils';
import { transpiler } from '../jmol/parser';
import { keywords } from '../jmol/keywords';
import { properties } from '../jmol/properties';
import { operators } from '../jmol/operators';
const general = {
supported: [
// atom expressions
'123',
'-42',
'_C',
'.CA',
'ALA',
'%A',
'^B',
':C',
'/2',
'10^A:F.CA%C/0',
'10^A:F.CA%C',
'10^A:F.CA',
'10^A:F',
'10^A',
'10:F.CA',
'10/0',
'32 or 42',
'.CA/0 OR 42:A',
'!23',
'not ASP',
'(ASP or .CA)',
'ASP and .CA',
'123.CA',
'(1 or 2) and .CA',
'(1 or 2) and (.CA or .N)',
'.CA and (2 or 3)',
'.CA and (2 or 3) and ^A',
'!32 or :A and .CA',
// trimming
' atomName = CA ',
'atomName = CA ',
' atomName = CA',
// value comparison
'resno > 10',
// atom expression
'[LEU]100:A.CA',
'[LEU]100:A',
'[LEU]100.CA',
'[LEU]:A.CA',
'[LEU].CA',
// comma as OR
'100, 42, ALA',
// residue numbering
'(1-10,15,21-30)',
// within
'within(5,[HEM])',
// within with parentheses
'(within(5,[HEM])) and backbone',
'( within(5,[HEM]) ) and backbone',
// trimming
'[ALA] and [VAL] ',
' [ALA] and [VAL] ',
' [ALA] and [VAL]',
// within with whitespaces
'within ( 5 , [HEM] ) ',
// un-braketed residue name
'LEU and ILE',
// un-parenthesized residue index range
'100-120,220',
// un-parenthesized residue index
'20',
// within in the head or the middle of sentence
'within ( 5 , [HEM] ) and backbone',
// atom expressions with ranges
'19-32:A',
'-2-32:B',
'-10--2:C',
'[1FO]19-32:A',
],
unsupported: [
// values outside of comparisons
'foobar',
'protein or foobar',
]
};
describe('jmol general', () => {
general.supported.forEach(str => {
it(str, () => {
transpiler(str);
});
});
general.unsupported.forEach(str => {
it(str, () => {
const transpileStr = () => transpiler(str);
expect(transpileStr).toThrow();
expect(transpileStr).not.toThrowError(RangeError);
});
});
});
describe('jmol keywords', () => u.testKeywords(keywords, transpiler));
describe('jmol properties', () => u.testProperties(properties, transpiler));
describe('jmol operators', () => u.testOperators(operators, transpiler));

View File

@@ -1,73 +0,0 @@
/**
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com>
* @author Koya Sakuma <koya.sakuma.work@gmail.com>
*/
import * as u from './utils';
import { transpiler } from '../pymol/parser';
import { keywords } from '../pymol/keywords';
import { properties } from '../pymol/properties';
import { operators } from '../pymol/operators';
const general = {
supported: [
// macros
'10/cb',
'a/10-12/ca',
'lig/b/6+8/c+o',
// trimming
' name CA ',
'name CA ',
' name CA',
],
unsupported: [
// macros
'pept/enz/c/3/n',
'pept/enz///n',
'/pept/lig/',
'/pept/lig/a',
'/pept/lig/a/10',
'/pept/lig/a/10/ca',
'/pept//a/10',
// object
'foobar',
'protein and bazbar',
]
};
describe('pymol general', () => {
general.supported.forEach(str => {
it(str, () => {
transpiler(str);
// compile(expr);
});
});
general.unsupported.forEach(str => {
it(str, () => {
const transpileStr = () => transpiler(str);
expect(transpileStr).toThrow();
expect(transpileStr).not.toThrowError(RangeError);
});
});
});
// check against builder output
// 'not (resi 42 or chain A)'
// '!resi 42 or chain A'
// 'b >= 0.3',
// 'b != 0.3',
// 'b>0.3',
// 'b <0.3',
// 'b <= 0.3',
// 'b = 1',
// 'fc.=.1',
describe('pymol keywords', () => u.testKeywords(keywords, transpiler));
describe('pymol operators', () => u.testOperators(operators, transpiler));
describe('pymol properties', () => u.testProperties(properties, transpiler));

View File

@@ -1,69 +0,0 @@
/**
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Panagiotis Tourlas <panangiot_tourlov@hotmail.com>
* @author Koya Sakuma <koya.sakuma.work@gmail.com>
*/
import { Transpiler } from '../transpiler';
import { KeywordDict, PropertyDict, OperatorList } from '../types';
export function testKeywords(keywords: KeywordDict, transpiler: Transpiler) {
for (const name in keywords) {
it(name, () => {
const k = keywords[name];
if (k.map) {
const expr = transpiler(name);
expect(expr).toEqual(k.map());
} else {
const transpile = () => transpiler(name);
expect(transpile).toThrow();
expect(transpile).not.toThrowError(RangeError);
}
});
}
}
export function testProperties(properties: PropertyDict, transpiler: Transpiler) {
for (const name in properties) {
const p = properties[name];
p['@examples'].forEach(example => {
it(name, () => {
if (!p.isUnsupported) {
transpiler(example);
} else {
const transpile = () => transpiler(example);
expect(transpile).toThrow();
expect(transpile).not.toThrowError(RangeError);
}
});
});
it(name, () => {
if (!p['@examples'].length) {
throw Error(`'${name}' property has no example(s)`);
}
});
}
}
export function testOperators(operators: OperatorList, transpiler: Transpiler) {
operators.forEach(o => {
o['@examples'].forEach(example => {
it(o.name, () => {
if (!o.isUnsupported) {
transpiler(example);
} else {
const transpile = () => transpiler(example);
expect(transpile).toThrow();
expect(transpile).not.toThrowError(RangeError);
}
});
});
it(o.name, () => {
if (!o['@examples'].length) {
throw Error(`'${o.name}' operator has no example(s)`);
}
});
});
}

View File

@@ -1,60 +0,0 @@
/**
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com>
* @author Koya Sakuma <koya.sakuma.work@gmail.com>
*/
import * as u from './utils';
import { transpiler } from '../vmd/parser';
import { keywords } from '../vmd/keywords';
import { properties } from '../vmd/properties';
import { operators } from '../vmd/operators';
const general = {
supported: [
// trimming
' name CA ',
'name CA ',
' name CA',
],
unsupported: [
// variables
'name $atomname',
'protein and @myselection',
// values outside of comparisons
'foobar',
'34',
'name',
'abs(-42)',
'abs(21+21)',
'sqr(3)',
'sqr(x)',
'sqr(x+33)',
'protein or foobar',
'34 and protein',
'name or protein',
]
};
describe('vmd general', () => {
general.supported.forEach(str => {
it(str, () => {
transpiler(str);
// compile(expr);
});
});
general.unsupported.forEach(str => {
it(str, () => {
const transpileStr = () => transpiler(str);
expect(transpileStr).toThrow();
expect(transpileStr).not.toThrowError(RangeError);
});
});
});
describe('vmd keywords', () => u.testKeywords(keywords, transpiler));
describe('vmd operators', () => u.testOperators(operators, transpiler));
describe('vmd properties', () => u.testProperties(properties, transpiler));

View File

@@ -1,17 +0,0 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*
* Adapted from MolQL project
*/
import { transpiler as jmol } from './jmol/parser';
import { transpiler as pymol } from './pymol/parser';
import { transpiler as vmd } from './vmd/parser';
export const _transpiler = {
pymol,
vmd,
jmol,
};

View File

@@ -1,385 +0,0 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com>
* @author Koya Sakuma <koya.sakuma.work@gmail.com>
*
* Adapted from MolQL project
*/
import * as P from '../../mol-util/monadic-parser';
import { MolScriptBuilder } from '../../mol-script/language/builder';
const B = MolScriptBuilder;
import { Expression } from '../language/expression';
import { KeywordDict, PropertyDict, FunctionDict, OperatorList } from './types';
export function escapeRegExp(s: String) {
return String(s).replace(/[\\^$*+?.()|[\]{}]/g, '\\$&');
}
// Takes a parser for the prefix operator, and a parser for the base thing being
// parsed, and parses as many occurrences as possible of the prefix operator.
// Note that the parser is created using `P.lazy` because it's recursive. It's
// valid for there to be zero occurrences of the prefix operator.
export function prefix(opParser: P.MonadicParser<any>, nextParser: P.MonadicParser<any>, mapFn: any) {
const parser: P.MonadicParser<any> = P.MonadicParser.lazy(() => {
return P.MonadicParser.seq(opParser, parser)
.map(x => mapFn(...x))
.or(nextParser);
});
return parser;
}
// Ideally this function would be just like `PREFIX` but reordered like
// `P.seq(parser, opParser).or(nextParser)`, but that doesn't work. The
// reason for that is that Parsimmon will get stuck in infinite recursion, since
// the very first rule. Inside `parser` is to match parser again. Alternatively,
// you might think to try `nextParser.or(P.seq(parser, opParser))`, but
// that won't work either because in a call to `.or` (aka `P.alt`), Parsimmon
// takes the first possible match, even if subsequent matches are longer, so the
// parser will never actually look far enough ahead to see the postfix
// operators.
export function postfix(opParser: P.MonadicParser<any>, nextParser: P.MonadicParser<any>, mapFn: any) {
// Because we can't use recursion like stated above, we just match a flat list
// of as many occurrences of the postfix operator as possible, then use
// `.reduce` to manually nest the list.
//
// Example:
//
// INPUT :: "4!!!"
// PARSE :: [4, "factorial", "factorial", "factorial"]
// REDUCE :: ["factorial", ["factorial", ["factorial", 4]]]
return P.MonadicParser.seqMap(
nextParser,
opParser.many(),
(x: any, suffixes: any) =>
suffixes.reduce((acc: any, x: any) => {
return mapFn(x, acc);
}, x)
);
}
// Takes a parser for all the operators at this precedence level, and a parser
// that parsers everything at the next precedence level, and returns a parser
// that parses as many binary operations as possible, associating them to the
// right. (e.g. 1^2^3 is 1^(2^3) not (1^2)^3)
export function binaryRight(opParser: P.MonadicParser<any>, nextParser: P.MonadicParser<any>, mapFn: any) {
const parser: P.MonadicParser<any> = P.MonadicParser.lazy(() =>
nextParser.chain(next =>
P.MonadicParser.seq(
opParser,
P.MonadicParser.of(next),
parser
).map((x) => {
return x;
}).or(P.MonadicParser.of(next))
)
);
return parser;
}
// Takes a parser for all the operators at this precedence level, and a parser
// that parsers everything at the next precedence level, and returns a parser
// that parses as many binary operations as possible, associating them to the
// left. (e.g. 1-2-3 is (1-2)-3 not 1-(2-3))
export function binaryLeft(opParser: P.MonadicParser<any>, nextParser: P.MonadicParser<any>, mapFn: any) {
// We run into a similar problem as with the `POSTFIX` parser above where we
// can't recurse in the direction we want, so we have to resort to parsing an
// entire list of operator chunks and then using `.reduce` to manually nest
// them again.
//
// Example:
//
// INPUT :: "1+2+3"
// PARSE :: [1, ["+", 2], ["+", 3]]
// REDUCE :: ["+", ["+", 1, 2], 3]
return P.MonadicParser.seqMap(
nextParser,
P.MonadicParser.seq(opParser, nextParser).many(),
(first: any, rest: any) => {
return rest.reduce((acc: any, ch: any) => {
const [op, another] = ch;
return mapFn(op, acc, another);
}, first);
}
);
}
/**
* combine operators of decreasing binding strength
*/
export function combineOperators(opList: any[], rule: P.MonadicParser<any>) {
const x = opList.reduce(
(acc, level) => {
const map = level.isUnsupported ? makeError(`operator '${level.name}' not supported`) : level.map;
return level.type(level.rule, acc, map);
},
rule
);
return x;
}
export function infixOp(re: RegExp, group: number = 0) {
return P.MonadicParser.optWhitespace.then(P.MonadicParser.regexp(re, group).skip(P.MonadicParser.optWhitespace));
}
export function prefixOp(re: RegExp, group: number = 0) {
return P.MonadicParser.regexp(re, group).skip(P.MonadicParser.optWhitespace);
}
export function postfixOp(re: RegExp, group: number = 0) {
return P.MonadicParser.optWhitespace.then(P.MonadicParser.regexp(re, group));
}
export function ofOp(name: string, short?: string) {
const op = short ? `${name}|${escapeRegExp(short)}` : name;
const re = RegExp(`(${op})\\s+([-+]?[0-9]*\\.?[0-9]+)\\s+OF`, 'i');
return infixOp(re, 2).map(parseFloat);
}
export function makeError(msg: string) {
return function () {
throw new Error(msg);
};
}
export function andExpr(selections: any[]) {
if (selections.length === 1) {
return selections[0];
} else if (selections.length > 1) {
return B.core.logic.and(selections);
} else {
return undefined;
}
}
export function orExpr(selections: any[]) {
if (selections.length === 1) {
return selections[0];
} else if (selections.length > 1) {
return B.core.logic.or(selections);
} else {
return undefined;
}
}
export function testExpr(property: any, args: any) {
if (args && args.op !== undefined && args.val !== undefined) {
const opArgs = [property, args.val];
switch (args.op) {
case '=': return B.core.rel.eq(opArgs);
case '!=': return B.core.rel.neq(opArgs);
case '>': return B.core.rel.gr(opArgs);
case '<': return B.core.rel.lt(opArgs);
case '>=': return B.core.rel.gre(opArgs);
case '<=': return B.core.rel.lte(opArgs);
default: throw new Error(`operator '${args.op}' not supported`);
}
} else if (args && args.flags !== undefined) {
return B.core.flags.hasAny([property, args.flags]);
} else if (args && args.min !== undefined && args.max !== undefined) {
return B.core.rel.inRange([property, args.min, args.max]);
} else if (!Array.isArray(args)) {
return B.core.rel.eq([property, args]);
} else if (args.length > 1) {
return B.core.set.has([B.core.type.set(args), property]);
} else {
return B.core.rel.eq([property, args[0]]);
}
}
export function invertExpr(selection: Expression) {
return B.struct.generator.queryInSelection({
0: selection, query: B.struct.generator.all(), 'in-complement': true }
);
}
export function strLenSortFn(a: string, b: string) {
return a.length < b.length ? 1 : -1;
}
function getNamesRegex(name: string, abbr?: string[]) {
const names = (abbr ? [name].concat(abbr) : [name])
.sort(strLenSortFn).map(escapeRegExp).join('|');
return RegExp(`${names}`, 'i');
}
export function getPropertyRules(properties: PropertyDict) {
// in keyof typeof properties
const propertiesDict: { [name: string]: P.MonadicParser<any> } = {};
Object.keys(properties).sort(strLenSortFn).forEach(name => {
const ps = properties[name];
const errorFn = makeError(`property '${name}' not supported`);
const rule = P.MonadicParser.regexp(ps.regex).map((x: any) => {
if (ps.isUnsupported) errorFn();
return testExpr(ps.property, ps.map(x));
});
if (!ps.isNumeric) {
propertiesDict[name] = rule;
}
});
return propertiesDict;
}
export function getNamedPropertyRules(properties: PropertyDict) {
const namedPropertiesList: P.MonadicParser<any>[] = [];
Object.keys(properties).sort(strLenSortFn).forEach(name => {
const ps = properties[name];
const errorFn = makeError(`property '${name}' not supported`);
const rule = P.MonadicParser.regexp(ps.regex).map((x: any) => {
if (ps.isUnsupported) errorFn();
return testExpr(ps.property, ps.map(x));
});
const nameRule = P.MonadicParser.regexp(getNamesRegex(name, ps.abbr)).trim(P.MonadicParser.optWhitespace);
const groupMap = (x: any) => B.struct.generator.atomGroups({ [ps.level]: x });
if (ps.isNumeric) {
namedPropertiesList.push(
nameRule.then(P.MonadicParser.seq(
P.MonadicParser.regexp(/>=|<=|=|!=|>|</).trim(P.MonadicParser.optWhitespace),
P.MonadicParser.regexp(ps.regex).map(ps.map)
)).map((x: any) => {
if (ps.isUnsupported) errorFn();
return testExpr(ps.property, { op: x[0], val: x[1] });
}).map(groupMap)
);
} else {
namedPropertiesList.push(nameRule.then(rule).map(groupMap));
}
});
return namedPropertiesList;
}
export function getKeywordRules(keywords: KeywordDict) {
const keywordsList: P.MonadicParser<any>[] = [];
Object.keys(keywords).sort(strLenSortFn).forEach(name => {
const ks = keywords[name];
const mapFn = ks.map ? ks.map : makeError(`keyword '${name}' not supported`);
const rule = P.MonadicParser.regexp(getNamesRegex(name, ks.abbr)).map(mapFn);
keywordsList.push(rule);
});
return keywordsList;
}
export function getFunctionRules(functions: FunctionDict, argRule: P.MonadicParser<any>) {
const functionsList: P.MonadicParser<any>[] = [];
const begRule = P.MonadicParser.regexp(/\(\s*/);
const endRule = P.MonadicParser.regexp(/\s*\)/);
Object.keys(functions).sort(strLenSortFn).forEach(name => {
const fs = functions[name];
const mapFn = fs.map ? fs.map : makeError(`function '${name}' not supported`);
const rule = P.MonadicParser.regexp(new RegExp(name, 'i')).skip(begRule).then(argRule).skip(endRule).map(mapFn);
functionsList.push(rule);
});
return functionsList;
}
export function getPropertyNameRules(properties: PropertyDict, lookahead: RegExp) {
const list: P.MonadicParser<any>[] = [];
Object.keys(properties).sort(strLenSortFn).forEach(name => {
const ps = properties[name];
const errorFn = makeError(`property '${name}' not supported`);
const rule = (P.MonadicParser as any).regexp(getNamesRegex(name, ps.abbr)).lookahead(lookahead).map(() => {
if (ps.isUnsupported) errorFn();
return ps.property;
});
list.push(rule);
});
return list;
}
export function getReservedWords(properties: PropertyDict, keywords: KeywordDict, operators: OperatorList, functions?: FunctionDict) {
const w: string[] = [];
for (const name in properties) {
w.push(name);
if (properties[name].abbr) w.push(...properties[name].abbr!);
}
for (const name in keywords) {
w.push(name);
if (keywords[name].abbr) w.push(...keywords[name].abbr!);
}
operators.forEach(o => {
w.push(o.name);
if (o.abbr) w.push(...o.abbr);
});
return w;
}
export function atomNameSet(ids: string[]) {
return B.core.type.set(ids.map(B.atomName));
}
export function asAtoms(e: Expression) {
return B.struct.generator.queryInSelection({
0: e,
query: B.struct.generator.all()
});
}
export function wrapValue(property: any, value: any, sstrucDict?: any) {
switch (property.head.name) {
case 'structure-query.atom-property.macromolecular.label_atom_id':
return B.atomName(value);
case 'structure-query.atom-property.core.element-symbol':
return B.es(value);
case 'structure-query.atom-property.macromolecular.secondary-structure-flags':
if (sstrucDict) {
value = [sstrucDict[value.toUpperCase()] || 'none'];
}
return B.struct.type.secondaryStructureFlags([value]);
default:
return value;
}
}
const propPrefix = 'structure-query.atom-property.macromolecular.';
const entityProps = ['entityKey', 'label_entity_id', 'entityType'];
const chainProps = ['chainKey', 'label_asym_id', 'label_entity_id', 'auth_asym_id', 'entityType'];
const residueProps = ['residueKey', 'label_comp_id', 'label_seq_id', 'auth_comp_id', 'auth_seq_id', 'pdbx_formal_charge', 'secondaryStructureKey', 'secondaryStructureFlags', 'isModified', 'modifiedParentName'];
export function testLevel(property: any) {
if (property.head.name.startsWith(propPrefix)) {
const name = property.head.name.substr(propPrefix.length);
if (entityProps.includes(name)) return 'entity-test';
if (chainProps.includes(name)) return 'chain-test';
if (residueProps.includes(name)) return 'residue-test';
}
return 'atom-test';
}
const flagProps = [
'structure-query.atom-property.macromolecular.secondary-structure-flags'
];
export function valuesTest(property: any, values: any[]) {
if (flagProps.includes(property.head.name)) {
const name = values[0].head;
const flags: any[] = [];
values.forEach(v => flags.push(...v.args[0]));
return B.core.flags.hasAny([property, { head: name, args: flags }]);
} else {
if (values.length === 1) {
return B.core.rel.eq([property, values[0]]);
} else if (values.length > 1) {
return B.core.set.has([B.core.type.set(values), property]);
}
}
}
export function resnameExpr(resnameList: string[]) {
return B.struct.generator.atomGroups({
'residue-test': B.core.set.has([
B.core.type.set(resnameList),
B.ammp('label_comp_id')
])
});
}

View File

@@ -1,28 +0,0 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
*
* Adapted from MolQL project
*/
export const examples = [{
name: 'Residue 50 or 135',
value: '50 or 135'
}, {
name: 'Atoms with no covalent bonds',
value: 'bondcount = 0'
}, {
name: 'All 3-10 helices',
value: 'substructure = "helix310"'
}, {
name: 'Metal atoms',
value: 'metal'
}, {
name: 'Atoms invloved in aromatic bonds',
value: 'isAromatic'
}, {
name: 'Pyrimidine residues',
value: 'pyrimidine'
}];

View File

@@ -1,571 +0,0 @@
/**
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Koya Sakuma <koya.sakuma.work@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*
* Adapted from MolQL project
*/
import { MolScriptBuilder } from '../../../mol-script/language/builder';
const B = MolScriptBuilder;
import * as h from '../helper';
import { KeywordDict } from '../types';
const ResDict = {
acidic: ['ASP', 'GLU'],
aliphatic: ['ALA', 'GLY', 'ILE', 'LEU', 'VAL'],
amino: ['ALA', 'ARG', 'ASN', 'ASP', 'CYS', 'GLN', 'GLU', 'GLY', 'HIS', 'ILE', 'LEU', 'LYS', 'MET', 'PHE', 'PRO', 'SER', 'THR', 'TRP', 'TYR', 'VAL', 'ASX', 'GLX', 'UNK'],
aromatic: ['HIS', 'PHE', 'TRP', 'TYR'],
basic: ['ARG', 'HIS', 'LYS'],
buried: ['ALA', 'CYS', 'ILE', 'LEU', 'MET', 'PHE', 'TRP', 'VAL'],
cg: ['CYT', 'C', 'GUA', 'G'],
cyclic: ['HIS', 'PHE', 'PRO', 'TRP', 'TYR'],
hydrophobic: ['ALA', 'GLY', 'ILE', 'LEU', 'MET', 'PHE', 'PRO', 'TRP', 'TYR', 'VAL'],
large: ['ARG', 'GLU', 'GLN', 'HIS', 'ILE', 'LEU', 'LYS', 'MET', 'PHE', 'TRP', 'TYR'],
medium: ['ASN', 'ASP', 'CYS', 'PRO', 'THR', 'VAL'],
small: ['ALA', 'GLY', 'SER'],
nucleic: ['G', 'C', 'A', 'T', 'U', 'I', 'DG', 'DC', 'DA', 'DT', 'DU', 'DI', '+G', '+C', '+A', '+T', '+U', '+I']
};
const Backbone = {
nucleic: ['P', "O3'", "O5'", "C5'", "C4'", "C3'", 'OP1', 'OP2', 'O3*', 'O5*', 'C5*', 'C4*', 'C3*',
"C2'", "C1'", "O4'", "O2'"],
protein: ['C', 'N', 'CA']
};
function nucleicExpr() {
return B.struct.combinator.merge([
B.struct.generator.atomGroups({
'residue-test': B.core.set.has([
B.set(...ResDict.nucleic),
B.ammp('label_comp_id')
])
}),
B.struct.filter.pick({
0: B.struct.generator.atomGroups({
'group-by': B.ammp('residueKey')
}),
test: B.core.logic.and([
B.core.rel.eq([B.struct.atomSet.atomCount(), 1]),
B.core.rel.eq([B.ammp('label_atom_id'), B.atomName('P')]),
])
}),
B.struct.filter.pick({
0: B.struct.generator.atomGroups({
'group-by': B.ammp('residueKey')
}),
test: B.core.logic.or([
B.core.set.isSubset([
h.atomNameSet(["C1'", "C2'", "O3'", "C3'", "C4'", "C5'", "O5'"]),
B.ammpSet('label_atom_id')
]),
B.core.set.isSubset([
h.atomNameSet(['C1*', 'C2*', 'O3*', 'C3*', 'C4*', 'C5*', 'O5*']),
B.ammpSet('label_atom_id')
])
])
})
]);
}
// TODO: improve, see keywords.protein['@desc'] below
function proteinExpr() {
return B.struct.generator.atomGroups({
'residue-test': B.core.set.has([
B.set(...ResDict.amino),
B.ammp('label_comp_id')
])
});
}
// TODO: improve, see keywords.backbone['@desc'] below
function backboneExpr() {
return B.struct.combinator.merge([
B.struct.modifier.intersectBy({
0: B.struct.generator.atomGroups({
'residue-test': B.core.set.has([
B.core.type.set(ResDict.amino),
B.ammp('label_comp_id')
])
}),
by: B.struct.generator.atomGroups({
'atom-test': B.core.set.has([
B.core.type.set(Backbone.protein),
B.ammp('label_atom_id')
])
})
}),
B.struct.modifier.intersectBy({
0: B.struct.generator.atomGroups({
'residue-test': B.core.set.has([
B.core.type.set(ResDict.nucleic),
B.ammp('label_comp_id')
])
}),
by: B.struct.generator.atomGroups({
'atom-test': B.core.set.has([
B.core.type.set(Backbone.nucleic),
B.ammp('label_atom_id')
])
})
}),
]);
}
export const keywords: KeywordDict = {
// general terms
all: {
'@desc': 'all atoms; same as *',
abbr: ['*'],
map: () => B.struct.generator.all()
},
bonded: {
'@desc': 'covalently bonded',
map: () => B.struct.generator.atomGroups({
'atom-test': B.core.rel.gr([B.struct.atomProperty.core.bondCount({
flags: B.struct.type.bondFlags(['covalent', 'metallic', 'sulfide'])
}), 0])
})
},
clickable: {
'@desc': 'actually visible -- having some visible aspect such as wireframe, spacefill, or a label showing, or the alpha-carbon or phosphorus atom in a biomolecule that is rendered with only cartoon, rocket, or other biomolecule-specific shape.'
},
connected: {
'@desc': 'bonded in any way, including hydrogen bonds',
map: () => B.struct.generator.atomGroups({
'atom-test': B.core.rel.gr([B.struct.atomProperty.core.bondCount({
flags: B.struct.type.bondFlags()
}), 0])
})
},
displayed: {
'@desc': 'displayed using the display or hide command; not necessarily visible'
},
hidden: {
'@desc': 'hidden using the display or hide command'
},
none: {
'@desc': 'no atoms',
map: () => B.struct.generator.empty()
},
selected: {
'@desc': 'atoms that have been selected; defaults to all when a file is first loaded'
},
thisModel: {
'@desc': 'atoms in the current frame set, as defined by frame, model, or animation commands. If more than one model is in this set, "thisModel" refers to all of them, regardless of atom displayed/hidden status.'
},
visible: {
'@desc': 'visible in any way, including PDB residue atoms for which a cartoon or other such rendering makes their group visible, even if they themselves are not visible.'
},
subset: {
'@desc': 'the currently defined subset. Note that if a subset is currently defined, then select/display all is the same as select/display subset, restrict none is the same as restrict not subset. In addition, select not subset selects nothing.'
},
specialPosition: {
'@desc': 'atoms in crystal structures that are at special positions - that is, for which there is more than one operator that leads to them.'
},
unitcell: {
'@desc': 'atoms within the current unitcell, which may be offset. This includes atoms on the faces and at the vertices of the unitcell.'
},
polyhedra: {
'@desc': 'all central atoms for which polyhedra have been created. See also polyhera(n), below. (Jmol 14.4)'
},
nonmetal: {
'@desc': '_H,_He,_B,_C,_N,_O,_F,_Ne,_Si,_P,_S,_Cl,_Ar,_As,_Se,_Br,_Kr,_Te,_I,_Xe,_At,_Rn',
map: () => B.struct.generator.atomGroups({
'atom-test': B.core.set.has([
B.set(...['H', 'He', 'B', 'C', 'N', 'O', 'F', 'Ne', 'Si', 'P', 'S', 'Cl', 'Ar', 'As', 'Se', 'Br', 'Kr', 'Te', 'I', 'Xe', 'At', 'Rn'].map(B.es)),
B.acp('elementSymbol')
])
})
},
metal: {
'@desc': '!nonmetal',
map: () => B.struct.generator.atomGroups({
'atom-test': B.core.logic.not([
B.core.set.has([
B.set(...['H', 'He', 'B', 'C', 'N', 'O', 'F', 'Ne', 'Si', 'P', 'S', 'Cl', 'Ar', 'As', 'Se', 'Br', 'Kr', 'Te', 'I', 'Xe', 'At', 'Rn'].map(B.es)),
B.acp('elementSymbol')
])
])
})
},
alkaliMetal: {
'@desc': '_Li,_Na,_K,_Rb,_Cs,_Fr',
map: () => B.struct.generator.atomGroups({
'atom-test': B.core.set.has([
B.set(...['Li', 'Na', 'K', 'Rb', 'Cs', 'Fr'].map(B.es)),
B.acp('elementSymbol')
])
})
},
alkalineEarth: {
'@desc': '_Be,_Mg,_Ca,_Sr,_Ba,_Ra',
map: () => B.struct.generator.atomGroups({
'atom-test': B.core.set.has([
B.set(...['Be', 'Mg', 'Ca', 'Sr', 'Ba', 'Ra'].map(B.es)),
B.acp('elementSymbol')
])
})
},
nobleGas: {
'@desc': '_He,_Ne,_Ar,_Kr,_Xe,_Rn',
map: () => B.struct.generator.atomGroups({
'atom-test': B.core.set.has([
B.set(...['He', 'Ne', 'Ar', 'Kr', 'Xe', 'Rn'].map(B.es)),
B.acp('elementSymbol')
])
})
},
metalloid: {
'@desc': '_B,_Si,_Ge,_As,_Sb,_Te',
map: () => B.struct.generator.atomGroups({
'atom-test': B.core.set.has([
B.set(...['B', 'Si', 'Ge', 'As', 'Sb', 'Te'].map(B.es)),
B.acp('elementSymbol')
])
})
},
transitionMetal: {
'@desc': '(includes La and Ac) elemno>=21 and elemno<=30, elemno=57, elemno=89, elemno>=39 and elemno<=48, elemno>=72 and elemno<=80, elemno>=104 and elemno<=112',
map: () => B.struct.generator.atomGroups({
'atom-test': B.core.logic.or([
B.core.rel.inRange([B.acp('atomicNumber'), 21, 30]),
B.core.rel.inRange([B.acp('atomicNumber'), 39, 48]),
B.core.rel.inRange([B.acp('atomicNumber'), 72, 80]),
B.core.rel.inRange([B.acp('atomicNumber'), 104, 112]),
B.core.set.has([B.set(57, 89), B.acp('atomicNumber')])
])
})
},
lanthanide: {
'@desc': '(does not include La) elemno>57 and elemno<=71',
map: () => B.struct.generator.atomGroups({
'atom-test': B.core.rel.inRange([B.acp('atomicNumber'), 57, 71])
})
},
actinide: {
'@desc': '(does not include Ac) elemno>89 and elemno<=103',
map: () => B.struct.generator.atomGroups({
'atom-test': B.core.rel.inRange([B.acp('atomicNumber'), 89, 103])
})
},
isaromatic: {
'@desc': 'atoms connected with the AROMATIC, AROMATICSINGLE, or AROMATICDOUBLE bond types',
map: () => B.struct.generator.atomGroups({
'atom-test': B.core.rel.gr([
B.struct.atomProperty.core.bondCount({
flags: B.struct.type.bondFlags(['aromatic'])
}),
0
])
})
},
carbohydrate: {
'@desc': ''
},
ions: {
'@desc': '(specifically the PDB designations "PO4" and "SO4")'
},
ligand: {
'@desc': '(originally "hetero and not solvent"; changed to "!(protein,nucleic,water,UREA)" for Jmol 12.2)'
},
nucleic: {
'@desc': 'any group that (a) has one of the following group names: G, C, A, T, U, I, DG, DC, DA, DT, DU, DI, +G, +C, +A, +T, +U, +I; or (b) can be identified as a group that is only one atom, with name "P"; or (c) has all of the following atoms (prime, \', can replace * here): C1*, C2*, C3*, O3*, C4*, C5*, and O5*.',
map: () => nucleicExpr()
},
purine: {
'@desc': 'any nucleic group that (a) has one of the following group names: A, G, I, DA, DG, DI, +A, +G, or +I; or (b) also has atoms N7, C8, and N9.',
map: () => B.struct.modifier.intersectBy({
0: nucleicExpr(),
by: B.struct.combinator.merge([
B.struct.generator.atomGroups({
'residue-test': B.core.set.has([
B.set(...['A', 'G', 'I', 'DA', 'DG', 'DI', '+A', '+G', '+I']),
B.ammp('label_comp_id')
])
}),
B.struct.filter.pick({
0: B.struct.generator.atomGroups({
'group-by': B.ammp('residueKey')
}),
test: B.core.set.isSubset([
h.atomNameSet(['N7', 'C8', 'N9']),
B.ammpSet('label_atom_id')
])
})
])
})
},
pyrimidine: {
'@desc': 'any nucleic group that (a) has one of the following group names: C, T, U, DC, DT, DU, +C, +T, +U; or (b) also has atom O2.',
map: () => B.struct.modifier.intersectBy({
0: nucleicExpr(),
by: B.struct.combinator.merge([
B.struct.generator.atomGroups({
'residue-test': B.core.set.has([
B.set(...['C', 'T', 'U', 'DC', 'DT', 'DU', '+C', '+T', '+U']),
B.ammp('label_comp_id')
])
}),
B.struct.filter.pick({
0: B.struct.generator.atomGroups({
'group-by': B.ammp('residueKey')
}),
test: B.core.logic.or([
B.core.set.has([
B.ammpSet('label_atom_id'),
B.atomName('O2*')
]),
B.core.set.has([
B.ammpSet('label_atom_id'),
B.atomName("O2'")
])
])
})
])
})
},
dna: {
'@desc': 'any nucleic group that (a) has one of the following group names: DG, DC, DA, DT, DU, DI, T, +G, +C, +A, +T; or (b) has neither atom O2* or O2\'.',
map: () => B.struct.modifier.intersectBy({
0: nucleicExpr(),
by: B.struct.combinator.merge([
B.struct.generator.atomGroups({
'residue-test': B.core.set.has([
B.set(...['DG', 'DC', 'DA', 'DT', 'DU', 'DI', 'T', '+G', '+C', '+A', '+T']),
B.ammp('label_comp_id')
])
}),
B.struct.filter.pick({
0: B.struct.generator.atomGroups({
'group-by': B.ammp('residueKey')
}),
test: B.core.logic.not([
B.core.logic.or([
B.core.set.has([
B.ammpSet('label_atom_id'),
B.atomName('O2*')
]),
B.core.set.has([
B.ammpSet('label_atom_id'),
B.atomName("O2'")
])
])
])
})
])
})
},
rna: {
'@desc': 'any nucleic group that (a) has one of the following group names: G, C, A, U, I, +U, +I; or (b) has atom O2* or O2\'.',
map: () => B.struct.modifier.intersectBy({
0: nucleicExpr(),
by: B.struct.combinator.merge([
B.struct.generator.atomGroups({
'residue-test': B.core.set.has([
B.set(...['G', 'C', 'A', 'U', 'I', '+U', '+I']),
B.ammp('label_comp_id')
])
}),
B.struct.filter.pick({
0: B.struct.generator.atomGroups({
'group-by': B.ammp('residueKey')
}),
test: B.core.logic.or([
B.core.set.has([
B.ammpSet('label_atom_id'),
B.atomName('O2*')
]),
B.core.set.has([
B.ammpSet('label_atom_id'),
B.atomName("O2'")
])
])
})
])
})
},
protein: {
'@desc': 'defined as a group that (a) has one of the following group names: ALA, ARG, ASN, ASP, CYS, GLN, GLU, GLY, HIS, ILE, LEU, LYS, MET, PHE, PRO, SER, THR, TRP, TYR, VAL, ASX, GLX, or UNK; or (b) contains PDB atom designations [C, O, CA, and N] bonded correctly; or (c) does not contain "O" but contains [C, CA, and N] bonded correctly; or (d) has only one atom, which has name CA and does not have the group name CA (indicating a calcium atom).',
map: () => proteinExpr()
},
acidic: {
'@desc': 'ASP GLU',
map: () => h.resnameExpr(ResDict.acidic)
},
acyclic: {
'@desc': 'amino and not cyclic',
map: () => B.struct.modifier.intersectBy({
0: h.resnameExpr(ResDict.amino),
by: h.invertExpr(h.resnameExpr(ResDict.cyclic))
})
},
aliphatic: {
'@desc': 'ALA GLY ILE LEU VAL',
map: () => h.resnameExpr(ResDict.aliphatic)
},
amino: {
'@desc': 'all twenty standard amino acids, plus ASX, GLX, UNK',
map: () => h.resnameExpr(ResDict.amino)
},
aromatic: {
'@desc': 'HIS PHE TRP TYR (see also "isaromatic" for aromatic bonds)',
map: () => h.resnameExpr(ResDict.aromatic)
},
basic: {
'@desc': 'ARG HIS LYS',
map: () => h.resnameExpr(ResDict.basic)
},
buried: {
'@desc': 'ALA CYS ILE LEU MET PHE TRP VAL',
map: () => h.resnameExpr(ResDict.buried)
},
charged: {
'@desc': 'same as acidic or basic -- ASP GLU, ARG HIS LYS',
map: () => h.resnameExpr(ResDict.acidic.concat(ResDict.basic))
},
cyclic: {
'@desc': 'HIS PHE PRO TRP TYR',
map: () => h.resnameExpr(ResDict.cyclic)
},
helix: {
'@desc': 'secondary structure-related.',
map: () => B.struct.generator.atomGroups({
'residue-test': B.core.flags.hasAny([
B.struct.type.secondaryStructureFlags(['helix']),
B.ammp('secondaryStructureFlags')
])
})
},
helixalpha: {
'@desc': 'secondary structure-related.',
map: () => B.struct.generator.atomGroups({
'residue-test': B.core.flags.hasAny([
B.struct.type.secondaryStructureFlags(['alpha']),
B.ammp('secondaryStructureFlags')
])
})
},
helix310: {
'@desc': 'secondary structure-related.',
map: () => B.struct.generator.atomGroups({
'residue-test': B.core.flags.hasAny([
B.struct.type.secondaryStructureFlags(['3-10']),
B.ammp('secondaryStructureFlags')
])
})
},
helixpi: {
'@desc': 'secondary structure-related.',
map: () => B.struct.generator.atomGroups({
'residue-test': B.core.flags.hasAny([
B.struct.type.secondaryStructureFlags(['pi']),
B.ammp('secondaryStructureFlags')
])
})
},
hetero: {
'@desc': 'PDB atoms designated as HETATM',
map: () => B.struct.generator.atomGroups({
'atom-test': B.ammp('isHet')
})
},
hydrophobic: {
'@desc': 'ALA GLY ILE LEU MET PHE PRO TRP TYR VAL',
map: () => h.resnameExpr(ResDict.hydrophobic)
},
large: {
'@desc': 'ARG GLU GLN HIS ILE LEU LYS MET PHE TRP TYR',
map: () => h.resnameExpr(ResDict.large)
},
medium: {
'@desc': 'ASN ASP CYS PRO THR VAL',
map: () => h.resnameExpr(ResDict.medium)
},
negative: {
'@desc': 'same as acidic -- ASP GLU',
map: () => h.resnameExpr(ResDict.acidic)
},
neutral: {
'@desc': 'amino and not (acidic or basic)',
map: () => B.struct.modifier.intersectBy({
0: h.resnameExpr(ResDict.amino),
by: h.invertExpr(h.resnameExpr(ResDict.acidic.concat(ResDict.basic)))
})
},
polar: {
'@desc': 'amino and not hydrophobic',
map: () => B.struct.modifier.intersectBy({
0: h.resnameExpr(ResDict.amino),
by: h.invertExpr(h.resnameExpr(ResDict.hydrophobic))
})
},
positive: {
'@desc': 'same as basic -- ARG HIS LYS',
map: () => h.resnameExpr(ResDict.basic)
},
sheet: {
'@desc': 'secondary structure-related',
map: () => B.struct.generator.atomGroups({
'residue-test': B.core.flags.hasAny([
B.struct.type.secondaryStructureFlags(['sheet']),
B.ammp('secondaryStructureFlags')
])
})
},
small: {
'@desc': 'ALA GLY SER',
map: () => h.resnameExpr(ResDict.small)
},
surface: {
'@desc': 'amino and not buried',
map: () => B.struct.modifier.intersectBy({
0: h.resnameExpr(ResDict.amino),
by: h.invertExpr(h.resnameExpr(ResDict.buried))
})
},
turn: {
'@desc': 'secondary structure-related',
map: () => B.struct.generator.atomGroups({
'residue-test': B.core.flags.hasAny([
B.struct.type.secondaryStructureFlags(['turn']),
B.ammp('secondaryStructureFlags')
])
})
},
alpha: {
'@desc': '(*.CA)',
map: () => B.struct.generator.atomGroups({
'atom-test': B.core.rel.eq([
B.atomName('CA'),
B.ammp('label_atom_id')
])
})
},
base: {
'@desc': '(nucleic bases)'
},
backbone: {
'@desc': '(*.C, *.CA, *.N, and all nucleic other than the bases themselves)',
abbr: ['mainchain'],
map: () => backboneExpr()
},
sidechain: {
'@desc': '((protein or nucleic) and not backbone)'
},
spine: {
'@desc': '(*.CA, *.N, *.C for proteins; *.P, *.O3\', *.O5\', *.C3\', *.C4\', *.C5 for nucleic acids)'
},
leadatom: {
'@desc': '(*.CA, *.P, and terminal *.O5\')'
},
solvent: {
'@desc': 'PDB "HOH", water, also the connected set of H-O-H in any model'
},
};

Some files were not shown because too many files have changed in this diff Show More