Compare commits

...

19 Commits

Author SHA1 Message Date
dsehnal
2faa821c50 2.0.0-dev.11 2021-03-19 17:29:29 +01:00
David Sehnal
7f355ae501 Merge pull request #141 from molstar/surrounding-ligands
Surrounding Ligands query
2021-03-19 17:16:25 +01:00
dsehnal
7f79ff9ff2 StructureSourceControls: show hierarchy preset is >1 trajectory is selected 2021-03-18 15:29:48 +01:00
dsehnal
02de871c59 StructureBoundingBox3D transform 2021-03-18 15:18:15 +01:00
dsehnal
00cb783d4c BoxShape3D transform 2021-03-18 14:15:04 +01:00
David Sehnal
c925919ee5 Merge pull request #148 from TomasKulhanek/master
FIX issue #147 CSS transform:scale cause molstar canvas to have incorrect size
2021-03-17 10:50:44 +01:00
dsehnal
324820890a Fix createModelProperty.isApplicable 2021-03-17 10:35:29 +01:00
Tomas Kulhanek
2687b29d4d FIX molstar/molstar#147 offsetWidth/offsetHeight is correct size of element when css transform:scale is used 2021-03-17 07:46:41 +00:00
dsehnal
7084aaee1a adjust text 2021-03-16 23:02:14 +01:00
dsehnal
520a2f7850 model-server: empty result console output 2021-03-16 22:47:34 +01:00
Alexander Rose
9264987817 camera helper tweaks
- add highlighting
- improved axes alignment
2021-03-15 23:16:19 -07:00
dsehnal
b736ed3ea4 readme tweaks 2021-03-15 21:35:17 +01:00
dsehnal
f12f5eca90 Merge branch 'master' into surrounding-ligands 2021-03-15 16:45:53 +01:00
dsehnal
aaafa1d5ad model-server: surroundingLigands query 2021-03-14 15:09:37 +01:00
dsehnal
a1d9a77653 surroundingLigands query 2021-03-14 15:00:13 +01:00
dsehnal
f2f1181af3 Merge branch 'master' into surrounding-ligands 2021-03-14 13:16:22 +01:00
dsehnal
73f6793bd8 surroundingLigands query wip 2021-03-14 12:22:01 +01:00
dsehnal
87ee9d88f2 ResidueSet helper (wip) 2021-03-14 11:21:12 +01:00
dsehnal
b1e245e913 add UndirectedGraph 2021-03-14 10:19:28 +01:00
24 changed files with 636 additions and 35 deletions

View File

@@ -5,15 +5,12 @@
# Mol*
The goal of **Mol\*** (*/'mol-star/*) is to provide a technology stack that will serve as a basis for the next-generation data delivery and analysis tools for macromolecular structure data. This is a collaboration between PDBe and RCSB PDB teams and the development will be open-source and available to anyone who wants to use it for developing visualization tools for macromolecular structure data available from [PDB](https://www.wwpdb.org/) and other institutions.
The goal of **Mol\*** (*/'mol-star/*) is to provide a technology stack that serves as a basis for the next-generation data delivery and analysis tools for (not only) macromolecular structure data. Mol* development was jointly initiated by PDBe and RCSB PDB to combine and build on the strengths of [LiteMol](https://litemol.org) (developed by PDBe) and [NGL](https://nglviewer.org) (developed by RCSB PDB) viewers.
This particular project is the implementation of this technology (still under development).
*If you are looking for the "MOLeculAR structure annoTator", that package is now available on NPM as [MolArt](https://www.npmjs.com/package/molart).*
## Project Structure Overview
## Project Overview
The core of Mol* currently consists of these modules (see under `src/`):
The core of Mol* consists of these modules (see under `src/`):
- `mol-task` Computation abstraction with progress tracking and cancellation support.
- `mol-data` Collections (integer-based sets, interface to columns/tables, etc.)
@@ -29,7 +26,6 @@ The core of Mol* currently consists of these modules (see under `src/`):
- `mol-gl` A wrapper around WebGL.
- `mol-canvas3d` A low-level 3d view component. Uses `mol-geo` to generate geometries.
- `mol-state` State representation tree with state saving and automatic updates.
- `mol-app` Components for building UIs.
- `mol-plugin` Allow to define modular Mol* plugin instances utilizing `mol-state` and `mol-canvas3d`.
- `mol-plugin-state` State transformations, builders, and managers.
- `mol-plugin-ui` React-based user interface for the Mol* plugin. Some components of the UI are usable outside the main plugin and can be integrated into 3rd party solutions.
@@ -41,7 +37,7 @@ Moreover, the project contains the implementation of `servers`, including
- `servers/volume` A tool for accessing volumetric experimental data related to molecular structures.
- `servers/plugin-state` A basic server to store Mol* Plugin states.
The project also contains performance tests (`perf-tests`), `examples`, and basic proof of concept `cli` apps (CIF to BinaryCIF converter and JSON domain annotation to CIF converter).
The project also contains performance tests (`perf-tests`), `examples`, and `cli` apps (CIF to BinaryCIF converter and JSON domain annotation to CIF converter).
## Previous Work
This project builds on experience from previous solutions:
@@ -169,9 +165,6 @@ To get syntax highlighting for shader and graphql files add the following to Vis
## Contributing
Just open an issue or make a pull request. All contributions are welcome.
## Roadmap
Continually develop this prototype project. As individual modules become stable, make them into standalone libraries.
## Funding
Funding sources include but are not limited to:
* [RCSB PDB](https://www.rcsb.org) funding by a grant [DBI-1338415; PI: SK Burley] from the NSF, the NIH, and the US DoE

4
package-lock.json generated
View File

@@ -1,11 +1,11 @@
{
"name": "molstar",
"version": "2.0.0-dev.10",
"version": "2.0.0-dev.11",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "2.0.0-dev.10",
"version": "2.0.0-dev.11",
"license": "MIT",
"dependencies": {
"@types/argparse": "^1.0.38",

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "2.0.0-dev.10",
"version": "2.0.0-dev.11",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {

View File

@@ -331,11 +331,13 @@ namespace Canvas3D {
changed = repr.mark(loci, action);
} else {
changed = helper.handle.mark(loci, action);
changed = helper.camera.mark(loci, action) || changed;
reprRenderObjects.forEach((_, _repr) => { changed = _repr.mark(loci, action) || changed; });
}
if (changed) {
scene.update(void 0, true);
helper.handle.scene.update(void 0, true);
helper.camera.scene.update(void 0, true);
const prevPickDirty = pickHelper.dirty;
draw(true);
pickHelper.dirty = prevPickDirty; // marking does not change picking buffers

View File

@@ -1,10 +1,11 @@
/**
* Copyright (c) 2020 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>
*/
import produce from 'immer';
import { Interval } from '../../mol-data/int/interval';
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
@@ -17,7 +18,9 @@ import { Sphere3D } from '../../mol-math/geometry';
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
import { DataLoci, EmptyLoci, Loci } from '../../mol-model/loci';
import { Shape } from '../../mol-model/shape';
import { Visual } from '../../mol-repr/visual';
import { ColorNames } from '../../mol-util/color/names';
import { MarkerAction, MarkerActions } from '../../mol-util/marker-action';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Camera, ICamera } from '../camera';
import { Viewport } from '../camera/util';
@@ -95,6 +98,26 @@ export class CameraHelper {
return CameraAxesLoci(this, groupId, instanceId);
}
private eachGroup = (loci: Loci, apply: (interval: Interval) => boolean): boolean => {
if (!this.renderObject) return false;
if (!isCameraAxesLoci(loci)) return false;
let changed = false;
const groupCount = this.renderObject.values.uGroupCount.ref.value;
const { elements } = loci;
for (const { groupId, instanceId } of elements) {
const idx = instanceId * groupCount + groupId;
if (apply(Interval.ofSingleton(idx))) changed = true;
}
return changed;
}
mark(loci: Loci, action: MarkerAction) {
if (!MarkerActions.is(MarkerActions.Highlighting, action)) return false;
if (!isCameraAxesLoci(loci)) return false;
if (loci.data !== this) return false;
return Visual.mark(this.renderObject, loci, action, this.eachGroup);
}
update(camera: ICamera) {
if (!this.renderObject) return;

View File

@@ -12,13 +12,13 @@ export function setCanvasSize(canvas: HTMLCanvasElement, width: number, height:
}
/** Resize canvas to container element taking `devicePixelRatio` into account */
export function resizeCanvas (canvas: HTMLCanvasElement, container: Element, scale = 1) {
export function resizeCanvas (canvas: HTMLCanvasElement, container: HTMLElement, scale = 1) {
let width = window.innerWidth;
let height = window.innerHeight;
if (container !== document.body) {
let bounds = container.getBoundingClientRect();
width = bounds.right - bounds.left;
height = bounds.bottom - bounds.top;
// fixes issue #molstar/molstar#147, offsetWidth/offsetHeight is correct size when css transform:scale is used
width = container.offsetWidth;
height = container.offsetHeight;
}
setCanvasSize(canvas, width, height, scale);
}

View File

@@ -61,7 +61,7 @@ namespace CustomElementProperty {
type: builder.type || 'dynamic',
defaultParams: {},
getParams: (data: Model) => ({}),
isApplicable: (data: Model) => !!builder.isApplicable?.(data),
isApplicable: (data: Model) => !builder.isApplicable || !!builder.isApplicable(data),
obtain: async (ctx: CustomProperty.Context, data: Model) => {
return await builder.getData(data, ctx);
}

View File

@@ -0,0 +1,107 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { StructureElement } from '../../../structure/element';
import { StructureProperties } from '../../../structure/properties';
export interface ResidueSetEntry {
label_asym_id: string,
label_comp_id: string,
label_seq_id: number,
label_alt_id: string,
ins_code: string,
// 1_555 by default
operator_name?: string
}
export class ResidueSet {
private index = new Map<string, Map<number, ResidueSetEntry[]>>();
private checkOperator: boolean = false;
add(entry: ResidueSetEntry) {
let root = this.index.get(entry.label_asym_id);
if (!root) {
root = new Map();
this.index.set(entry.label_asym_id, root);
}
let entries = root.get(entry.label_seq_id);
if (!entries) {
entries = [];
root.set(entry.label_seq_id, entries);
}
const exists = this._find(entry, entries);
if (!exists) {
entries.push(entry);
return true;
}
return false;
}
hasLabelAsymId(asym_id: string) {
return this.index.has(asym_id);
}
has(loc: StructureElement.Location) {
const asym_id = _asym_id(loc);
if (!this.index.has(asym_id)) return;
const root = this.index.get(asym_id)!;
const seq_id = _seq_id(loc);
if (!root.has(seq_id)) return;
const entries = root.get(seq_id)!;
const comp_id = _comp_id(loc);
const alt_id = _alt_id(loc);
const ins_code = _ins_code(loc);
const op_name = _op_name(loc) ?? '1_555';
for (const e of entries) {
if (e.label_comp_id !== comp_id || e.label_alt_id !== alt_id || e.ins_code !== ins_code) continue;
if (this.checkOperator && (e.operator_name ?? '1_555') !== op_name) continue;
return e;
}
}
static getLabel(entry: ResidueSetEntry, checkOperator = false) {
return `${entry.label_asym_id} ${entry.label_comp_id} ${entry.label_seq_id}:${entry.ins_code}:${entry.label_alt_id}${checkOperator ? ' ' + (entry.operator_name ?? '1_555') : ''}`;
}
static getEntryFromLocation(loc: StructureElement.Location): ResidueSetEntry {
return {
label_asym_id: _asym_id(loc),
label_comp_id: _comp_id(loc),
label_seq_id: _seq_id(loc),
label_alt_id: _alt_id(loc),
ins_code: _ins_code(loc),
operator_name: _op_name(loc) ?? '1_555'
};
}
private _find(entry: ResidueSetEntry, xs: ResidueSetEntry[]) {
for (const e of xs) {
if (e.label_comp_id !== entry.label_comp_id || e.label_alt_id !== entry.label_alt_id || e.ins_code !== entry.ins_code) continue;
if (this.checkOperator && (e.operator_name ?? '1_555') !== (entry.operator_name ?? '1_555')) continue;
return true;
}
return false;
}
constructor(options?: { checkOperator?: boolean }) {
this.checkOperator = options?.checkOperator ?? false;
}
}
const _asym_id = StructureProperties.chain.label_asym_id;
const _seq_id = StructureProperties.residue.label_seq_id;
const _comp_id = StructureProperties.atom.label_comp_id;
const _alt_id = StructureProperties.atom.label_alt_id;
const _ins_code = StructureProperties.residue.pdbx_PDB_ins_code;
const _op_name = StructureProperties.unit.operator_name;

View File

@@ -11,10 +11,14 @@ import { StructureSelection } from '../selection';
import { UniqueStructuresBuilder } from '../utils/builders';
import { StructureUniqueSubsetBuilder } from '../../structure/util/unique-subset-builder';
import { QueryContext, QueryFn } from '../context';
import { structureIntersect, structureSubtract } from '../utils/structure-set';
import { structureIntersect, structureSubtract, structureUnion } from '../utils/structure-set';
import { UniqueArray } from '../../../../mol-data/generic';
import { StructureSubsetBuilder } from '../../structure/util/subset-builder';
import { StructureElement } from '../../structure/element';
import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
import { ResidueSet, ResidueSetEntry } from '../../model/properties/utils/residue-set';
import { StructureProperties } from '../../structure/properties';
import { arraySetAdd } from '../../../../mol-util/array';
function getWholeResidues(ctx: QueryContext, source: Structure, structure: Structure) {
const builder = source.subsetBuilder(true);
@@ -435,4 +439,252 @@ function expandConnected(ctx: QueryContext, structure: Structure) {
return builder.getStructure();
}
export interface SurroundingLigandsParams {
query: StructureQuery,
radius: number,
includeWater: boolean
}
/**
* Includes expanded surrounding ligands based on radius from the source, struct_conn entries & pdbx_molecule entries.
*/
export function surroundingLigands({ query, radius, includeWater }: SurroundingLigandsParams): StructureQuery {
return function query_surroundingLigands(ctx) {
const inner = StructureSelection.unionStructure(query(ctx));
const surroundings = getWholeResidues(ctx, ctx.inputStructure, getIncludeSurroundings(ctx, ctx.inputStructure, inner, { radius }));
const prd = getPrdAsymIdx(ctx.inputStructure);
const graph = getStructConnInfo(ctx.inputStructure);
const l = StructureElement.Location.create(surroundings);
const includedPrdChains = new Map<string, string[]>();
const componentResidues = new ResidueSet({ checkOperator: true });
for (const unit of surroundings.units) {
if (unit.kind !== Unit.Kind.Atomic) continue;
l.unit = unit;
const { elements } = unit;
const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
while (chainsIt.hasNext) {
const chainSegment = chainsIt.move();
l.element = elements[chainSegment.start];
const asym_id = StructureProperties.chain.label_asym_id(l);
const op_name = StructureProperties.unit.operator_name(l);
// check for PRD molecules
if (prd.has(asym_id)) {
if (includedPrdChains.has(asym_id)) {
arraySetAdd(includedPrdChains.get(asym_id)!, op_name);
} else {
includedPrdChains.set(asym_id, [op_name]);
}
continue;
}
const entityType = StructureProperties.entity.type(l);
// test entity and chain
if (entityType === 'water' || entityType === 'polymer') continue;
residuesIt.setSegment(chainSegment);
while (residuesIt.hasNext) {
const residueSegment = residuesIt.move();
l.element = elements[residueSegment.start];
graph.addComponent(ResidueSet.getEntryFromLocation(l), componentResidues);
}
}
ctx.throwIfTimedOut();
}
// assemble the core structure
const builder = ctx.inputStructure.subsetBuilder(true);
for (const unit of ctx.inputStructure.units) {
if (unit.kind !== Unit.Kind.Atomic) continue;
l.unit = unit;
const { elements } = unit;
const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
builder.beginUnit(unit.id);
while (chainsIt.hasNext) {
const chainSegment = chainsIt.move();
l.element = elements[chainSegment.start];
const asym_id = StructureProperties.chain.label_asym_id(l);
const op_name = StructureProperties.unit.operator_name(l);
if (includedPrdChains.has(asym_id) && includedPrdChains.get(asym_id)!.indexOf(op_name) >= 0) {
builder.addElementRange(elements, chainSegment.start, chainSegment.end);
continue;
}
if (!componentResidues.hasLabelAsymId(asym_id)) {
continue;
}
residuesIt.setSegment(chainSegment);
while (residuesIt.hasNext) {
const residueSegment = residuesIt.move();
l.element = elements[residueSegment.start];
if (!componentResidues.has(l)) continue;
builder.addElementRange(elements, residueSegment.start, residueSegment.end);
}
}
builder.commitUnit();
ctx.throwIfTimedOut();
}
const components = structureUnion(ctx.inputStructure, [builder.getStructure(), inner]);
// add water
if (includeWater) {
const finalBuilder = new StructureUniqueSubsetBuilder(ctx.inputStructure);
const lookup = ctx.inputStructure.lookup3d;
for (const unit of components.units) {
const { x, y, z } = unit.conformation;
const elements = unit.elements;
for (let i = 0, _i = elements.length; i < _i; i++) {
const e = elements[i];
lookup.findIntoBuilderIf(x(e), y(e), z(e), radius, finalBuilder, testIsWater);
finalBuilder.addToUnit(unit.id, e);
}
ctx.throwIfTimedOut();
}
return StructureSelection.Sequence(ctx.inputStructure, [finalBuilder.getStructure()]);
} else {
return StructureSelection.Sequence(ctx.inputStructure, [components]);
}
};
}
const _entity_type = StructureProperties.entity.type;
function testIsWater(l: StructureElement.Location) {
return _entity_type(l) === 'water';
}
function getPrdAsymIdx(structure: Structure) {
const model = structure.models[0];
const ids = new Set<string>();
if (!MmcifFormat.is(model.sourceData)) return ids;
const { _rowCount, asym_id } = model.sourceData.data.db.pdbx_molecule;
for (let i = 0; i < _rowCount; i++) {
ids.add(asym_id.value(i));
}
return ids;
}
function getStructConnInfo(structure: Structure) {
const model = structure.models[0];
const graph = new StructConnGraph();
if (!MmcifFormat.is(model.sourceData)) return graph;
const struct_conn = model.sourceData.data.db.struct_conn;
const { conn_type_id } = struct_conn;
const { ptnr1_label_asym_id, ptnr1_label_comp_id, ptnr1_label_seq_id, ptnr1_symmetry, pdbx_ptnr1_label_alt_id, pdbx_ptnr1_PDB_ins_code } = struct_conn;
const { ptnr2_label_asym_id, ptnr2_label_comp_id, ptnr2_label_seq_id, ptnr2_symmetry, pdbx_ptnr2_label_alt_id, pdbx_ptnr2_PDB_ins_code } = struct_conn;
for (let i = 0; i < struct_conn._rowCount; i++) {
const bondType = conn_type_id.value(i);
if (bondType !== 'covale' && bondType !== 'metalc') continue;
const a: ResidueSetEntry = {
label_asym_id: ptnr1_label_asym_id.value(i),
label_comp_id: ptnr1_label_comp_id.value(i),
label_seq_id: ptnr1_label_seq_id.value(i),
label_alt_id: pdbx_ptnr1_label_alt_id.value(i),
ins_code: pdbx_ptnr1_PDB_ins_code.value(i),
operator_name: ptnr1_symmetry.value(i) ?? '1_555'
};
const b: ResidueSetEntry = {
label_asym_id: ptnr2_label_asym_id.value(i),
label_comp_id: ptnr2_label_comp_id.value(i),
label_seq_id: ptnr2_label_seq_id.value(i),
label_alt_id: pdbx_ptnr2_label_alt_id.value(i),
ins_code: pdbx_ptnr2_PDB_ins_code.value(i),
operator_name: ptnr2_symmetry.value(i) ?? '1_555'
};
graph.addEdge(a, b);
}
return graph;
}
class StructConnGraph {
vertices = new Map<string, ResidueSetEntry>();
edges = new Map<string, string[]>();
private addVertex(e: ResidueSetEntry, label: string) {
if (this.vertices.has(label)) return;
this.vertices.set(label, e);
this.edges.set(label, []);
}
addEdge(a: ResidueSetEntry, b: ResidueSetEntry) {
const al = ResidueSet.getLabel(a);
const bl = ResidueSet.getLabel(b);
this.addVertex(a, al);
this.addVertex(b, bl);
arraySetAdd(this.edges.get(al)!, bl);
arraySetAdd(this.edges.get(bl)!, al);
}
addComponent(start: ResidueSetEntry, set: ResidueSet) {
const startLabel = ResidueSet.getLabel(start);
if (!this.vertices.has(startLabel)) {
set.add(start);
return;
}
const visited = new Set<string>();
const added = new Set<string>();
const stack = [startLabel];
added.add(startLabel);
set.add(start);
while (stack.length > 0) {
const a = stack.pop()!;
visited.add(a);
const u = this.vertices.get(a)!;
for (const b of this.edges.get(a)!) {
if (visited.has(b)) continue;
stack.push(b);
if (added.has(b)) continue;
added.add(b);
const v = this.vertices.get(b)!;
if (u.operator_name === v.operator_name) {
set.add({ ...v, operator_name: start.operator_name });
} else {
set.add(v);
}
}
}
}
}
// TODO: unionBy (skip this one?), cluster

View File

@@ -94,6 +94,36 @@ export class StructureLookup3D {
}
}
findIntoBuilderIf(x: number, y: number, z: number, radius: number, builder: StructureUniqueSubsetBuilder, test: (l: StructureElement.Location) => boolean) {
const { units } = this.structure;
const closeUnits = this.unitLookup.find(x, y, z, radius);
if (closeUnits.count === 0) return;
const loc = StructureElement.Location.create(this.structure);
for (let t = 0, _t = closeUnits.count; t < _t; t++) {
const unit = units[closeUnits.indices[t]];
Vec3.set(this.pivot, x, y, z);
if (!unit.conformation.operator.isIdentity) {
Vec3.transformMat4(this.pivot, this.pivot, unit.conformation.operator.inverse);
}
const unitLookup = unit.lookup3d;
const groupResult = unitLookup.find(this.pivot[0], this.pivot[1], this.pivot[2], radius);
if (groupResult.count === 0) continue;
const elements = unit.elements;
loc.unit = unit;
builder.beginUnit(unit.id);
for (let j = 0, _j = groupResult.count; j < _j; j++) {
loc.element = elements[groupResult.indices[j]];
if (test(loc)) {
builder.addElement(loc.element);
}
}
builder.commitUnit();
}
}
findIntoBuilderWithRadius(x: number, y: number, z: number, pivotR: number, maxRadius: number, radius: number, eRadius: StructureElement.Property<number>, builder: StructureUniqueSubsetBuilder) {
const { units } = this.structure;
const closeUnits = this.unitLookup.find(x, y, z, radius);

View File

@@ -425,6 +425,18 @@ const surroundings = StructureSelectionQuery('Surrounding Residues (5 \u212B) of
referencesCurrent: true
});
const surroundingLigands = StructureSelectionQuery('Surrounding Ligands (5 \u212B) of Selection', MS.struct.modifier.union([
MS.struct.modifier.surroundingLigands({
0: MS.internal.generator.current(),
radius: 5,
'include-water': true
})
]), {
description: 'Select ligand components within 5 \u212B of the current selection.',
category: StructureSelectionCategory.Manipulate,
referencesCurrent: true
});
const complement = StructureSelectionQuery('Inverse / Complement of Selection', MS.struct.modifier.union([
MS.struct.modifier.exceptBy({
0: MS.struct.generator.all(),
@@ -645,6 +657,7 @@ export const StructureSelectionQueries = {
ring,
aromaticRing,
surroundings,
surroundingLigands,
complement,
covalentlyBonded,
covalentlyOrMetallicBonded,

View File

@@ -9,13 +9,15 @@ import * as Misc from './transforms/misc';
import * as Model from './transforms/model';
import * as Volume from './transforms/volume';
import * as Representation from './transforms/representation';
import * as Shape from './transforms/shape';
export const StateTransforms = {
Data,
Misc,
Model,
Volume,
Representation
Representation,
Shape
};
export type StateTransforms = typeof StateTransforms

View File

@@ -36,6 +36,10 @@ import { DihedralParams, DihedralRepresentation } from '../../mol-repr/shape/loc
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
import { Clipping } from '../../mol-theme/clipping';
import { ObjectKeys } from '../../mol-util/type-helpers';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { getBoxMesh } from './shape';
import { Shape } from '../../mol-model/shape';
import { Box3D } from '../../mol-math/geometry';
export { StructureRepresentation3D };
export { ExplodeStructureRepresentation3D };
@@ -736,6 +740,41 @@ const ModelUnitcell3D = PluginStateTransform.BuiltIn({
}
});
export { StructureBoundingBox3D };
type StructureBoundingBox3D = typeof StructureBoundingBox3D
const StructureBoundingBox3D = PluginStateTransform.BuiltIn({
name: 'structure-bounding-box-3d',
display: 'Bounding Box',
from: SO.Molecule.Structure,
to: SO.Shape.Representation3D,
params: {
radius: PD.Numeric(0.05, { min: 0.01, max: 4, step: 0.01 }, { isEssential: true }),
color: PD.Color(ColorNames.red, { isEssential: true }),
...Mesh.Params,
}
})({
canAutoUpdate() {
return true;
},
apply({ a, params }, plugin: PluginContext) {
return Task.create('Bounding Box', async ctx => {
const repr = ShapeRepresentation((_, data: { box: Box3D, radius: number, color: Color }, __, shape) => {
const mesh = getBoxMesh(data.box, data.radius, shape?.geometry);
return Shape.create('Bouding Box', data, mesh, () => data.color, () => 1, () => 'Bounding Box');
}, Mesh.Utils);
await repr.createOrUpdate(params, { box: a.data.boundary.box, radius: params.radius, color: params.color }).runInContext(ctx);
return new SO.Shape.Representation3D({ repr, source: a }, { label: `Bounding Box` });
});
},
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
return Task.create('Bounding Box', async ctx => {
await b.data.repr.createOrUpdate(newParams, { box: a.data.boundary.box, radius: newParams.radius, color: newParams.color }).runInContext(ctx);
b.data.source = a;
return StateTransformer.UpdateResult.Updated;
});
}
});
export { StructureSelectionsDistance3D };
type StructureSelectionsDistance3D = typeof StructureSelectionsDistance3D
const StructureSelectionsDistance3D = PluginStateTransform.BuiltIn({

View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { BoxCage } from '../../mol-geo/primitive/box';
import { Box3D, Sphere3D } from '../../mol-math/geometry';
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
import { Shape } from '../../mol-model/shape';
import { Task } from '../../mol-task';
import { ColorNames } from '../../mol-util/color/names';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { PluginStateObject as SO, PluginStateTransform } from '../objects';
export { BoxShape3D };
type BoxShape3D = typeof BoxShape3D
const BoxShape3D = PluginStateTransform.BuiltIn({
name: 'box-shape-3d',
display: 'Box Shape',
from: SO.Root,
to: SO.Shape.Provider,
params: {
bottomLeft: PD.Vec3(Vec3()),
topRight: PD.Vec3(Vec3.create(1, 1, 1)),
radius: PD.Numeric(0.15, { min: 0.01, max: 4, step: 0.01 }),
color: PD.Color(ColorNames.red)
}
})({
canAutoUpdate() {
return true;
},
apply({ params }) {
return Task.create('Shape Representation', async ctx => {
return new SO.Shape.Provider({
label: 'Box',
data: params,
params: Mesh.Params,
getShape: (_, data: typeof params) => {
const mesh = getBoxMesh(Box3D.create(params.bottomLeft, params.topRight), params.radius);
return Shape.create('Box', data, mesh, () => data.color, () => 1, () => 'Box');
},
geometryUtils: Mesh.Utils
}, { label: 'Box' });
});
}
});
export function getBoxMesh(box: Box3D, radius: number, oldMesh?: Mesh) {
const diag = Vec3.sub(Vec3(), box.max, box.min);
const translateUnit = Mat4.fromTranslation(Mat4(), Vec3.create(0.5, 0.5, 0.5));
const scale = Mat4.fromScaling(Mat4(), diag);
const translate = Mat4.fromTranslation(Mat4(), box.min);
const transform = Mat4.mul3(Mat4(), translate, scale, translateUnit);
// TODO: optimize?
const state = MeshBuilder.createState(256, 128, oldMesh);
state.currentGroup = 1;
MeshBuilder.addCage(state, transform, BoxCage(), radius, 2, 20);
const mesh = MeshBuilder.getMesh(state);
const center = Vec3.scaleAndAdd(Vec3(), box.min, diag, 0.5);
const sphereRadius = Vec3.distance(box.min, center);
mesh.setBoundingSphere(Sphere3D.create(center, sphereRadius));
return mesh;
}

View File

@@ -195,12 +195,27 @@ export class StructureSourceControls extends CollapsableControls<{}, StructureSo
get presetActions() {
const actions: ActionMenu.Item[] = [];
const { trajectories } = this.plugin.managers.structure.hierarchy.selection;
if (trajectories.length !== 1) return actions;
if (trajectories.length === 0) return actions;
let providers = this.plugin.builders.structure.hierarchy.getPresets(trajectories[0].cell.obj);
if (trajectories.length > 1) {
const providerSet = new Set(providers);
for (let i = 1; i < trajectories.length; i++) {
const providers = this.plugin.builders.structure.hierarchy.getPresets(trajectories[i].cell.obj);
const current = new Set(providers);
for (const p of providers) {
if (!current.has(p)) providerSet.delete(p);
}
}
providers = providers.filter(p => providerSet.has(p));
}
const providers = this.plugin.builders.structure.hierarchy.getPresets(trajectories[0].cell.obj);
for (const p of providers) {
actions.push(ActionMenu.Item(p.display.name, p, { description: p.display.description }));
}
return actions;
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 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 David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -83,12 +83,22 @@ export const CameraAxisHelper = PluginBehavior.create<{}>({
lastPlane = CameraHelperAxis.None;
state = 0;
return;
} else if (axis >= CameraHelperAxis.X && axis <= CameraHelperAxis.Z) {
}
const { camera } = this.ctx.canvas3d;
let dir: Vec3, up: Vec3;
if (axis >= CameraHelperAxis.X && axis <= CameraHelperAxis.Z) {
lastPlane = CameraHelperAxis.None;
state = 0;
const up = Vec3();
const d = Vec3.sub(Vec3(), camera.target, camera.position);
const c = Vec3.cross(Vec3(), d, camera.up);
up = Vec3();
up[axis - 1] = 1;
this.ctx.canvas3d.requestCameraReset({ snapshot: { up } });
dir = Vec3.cross(Vec3(), up, c);
if (Vec3.magnitude(dir) === 0) dir = d;
} else {
if (lastPlane === axis) {
state = (state + 1) % 2;
@@ -97,7 +107,6 @@ export const CameraAxisHelper = PluginBehavior.create<{}>({
state = 0;
}
let up: Vec3, dir: Vec3;
if (axis === CameraHelperAxis.XY) {
up = state ? Vec3.unitX : Vec3.unitY;
dir = Vec3.negUnitZ;
@@ -108,11 +117,11 @@ export const CameraAxisHelper = PluginBehavior.create<{}>({
up = state ? Vec3.unitY : Vec3.unitZ;
dir = Vec3.negUnitX;
}
this.ctx.canvas3d.requestCameraReset({
snapshot: (scene, camera) => camera.getInvariantFocus(scene.boundingSphereVisible.center, scene.boundingSphereVisible.radius, up, dir)
});
}
this.ctx.canvas3d.requestCameraReset({
snapshot: (scene, camera) => camera.getInvariantFocus(scene.boundingSphereVisible.center, scene.boundingSphereVisible.radius, up, dir)
});
});
}
},

View File

@@ -93,6 +93,7 @@ export const DefaultPluginSpec = (): PluginSpec => ({
PluginSpec.Action(StateTransforms.Representation.StructureSelectionsLabel3D),
PluginSpec.Action(StateTransforms.Representation.StructureSelectionsOrientation3D),
PluginSpec.Action(StateTransforms.Representation.ModelUnitcell3D),
PluginSpec.Action(StateTransforms.Representation.StructureBoundingBox3D),
PluginSpec.Action(StateTransforms.Representation.ExplodeStructureRepresentation3D),
PluginSpec.Action(StateTransforms.Representation.UnwindStructureAssemblyRepresentation3D),
PluginSpec.Action(StateTransforms.Representation.OverpaintStructureRepresentation3DFromScript),

View File

@@ -155,6 +155,12 @@ const modifier = {
'as-whole-residues': Argument(Type.Bool, { isOptional: true })
}), Types.ElementSelectionQuery, 'For each atom set in the selection, include all surrouding atoms/residues that are within the specified radius.'),
surroundingLigands: symbol(Arguments.Dictionary({
0: Argument(Types.ElementSelectionQuery),
radius: Argument(Type.Num),
'include-water': Argument(Type.Bool, { isOptional: true, defaultValue: true })
}), Types.ElementSelectionQuery, 'Find all ligands components around the source query.'),
includeConnected: symbol(Arguments.Dictionary({
0: Argument(Types.ElementSelectionQuery),
'bond-test': Argument(Type.Bool, { isOptional: true, defaultValue: 'true for covalent bonds' as any }),

View File

@@ -258,6 +258,13 @@ const symbols = [
elementRadius: xs['atom-radius']
})(ctx);
}),
D(MolScript.structureQuery.modifier.surroundingLigands, function structureQuery_modifier_includeSurroundingLigands(ctx, xs) {
return Queries.modifiers.surroundingLigands({
query: xs[0] as any,
radius: xs['radius'](ctx),
includeWater: !!(xs['include-water'] && xs['include-water'](ctx)),
})(ctx);
}),
D(MolScript.structureQuery.modifier.wholeResidues, function structureQuery_modifier_wholeResidues(ctx, xs) { return Queries.modifiers.wholeResidues(xs[0] as any)(ctx); }),
D(MolScript.structureQuery.modifier.union, function structureQuery_modifier_union(ctx, xs) { return Queries.modifiers.union(xs[0] as any)(ctx); }),
D(MolScript.structureQuery.modifier.expandProperty, function structureQuery_modifier_expandProperty(ctx, xs) { return Queries.modifiers.expandProperty(xs[0] as any, xs['property'])(ctx); }),

View File

@@ -138,6 +138,7 @@ export const SymbolTable = [
Alias(MolScript.structureQuery.modifier.union, 'sel.atom.union'),
Alias(MolScript.structureQuery.modifier.cluster, 'sel.atom.cluster'),
Alias(MolScript.structureQuery.modifier.includeSurroundings, 'sel.atom.include-surroundings'),
Alias(MolScript.structureQuery.modifier.surroundingLigands, 'sel.atom.surrounding-ligands'),
Alias(MolScript.structureQuery.modifier.includeConnected, 'sel.atom.include-connected'),
Alias(MolScript.structureQuery.modifier.expandProperty, 'sel.atom.expand-property'),

View File

@@ -1,3 +1,6 @@
# 0.9.7
* add Surrounding Ligands query
# 0.9.6
* optional download parameter

View File

@@ -136,6 +136,13 @@ const AssemblyNameParam: QueryParamInfo = {
description: 'Assembly name. If none is provided, crystal symmetry (where available) or deposited model is used.'
};
const OmitWaterParam: QueryParamInfo = {
name: 'omit_water',
type: QueryParamType.Boolean,
required: false,
defaultValue: false
};
function Q<Params = any>(definition: Partial<QueryDefinition<Params>>) {
return definition;
}
@@ -223,7 +230,28 @@ const QueryMap = {
jsonParams: [ AtomSiteTestJsonParam, RadiusParam ],
restParams: [ ...AtomSiteTestRestParams, RadiusParam ],
filter: QuerySchemas.interaction
})
}),
'surroundingLigands': Q<{ atom_site: AtomSiteSchema, radius: number, assembly_name: string, omit_water: boolean }>({
niceName: 'Surrounding Ligands',
description: 'Identifies (complete) ligands within the given radius from the source atom set. Takes crystal symmetry into account.',
query(p) {
const tests = getAtomsTests(p.atom_site);
const center = Queries.combinators.merge(tests.map(test => Queries.generators.atoms({
...test,
entityTest: test.entityTest
? ctx => test.entityTest!(ctx) && ctx.element.unit.conformation.operator.isIdentity
: ctx => ctx.element.unit.conformation.operator.isIdentity
})));
return Queries.modifiers.surroundingLigands({ query: center, radius: p.radius !== void 0 ? p.radius : 5, includeWater: !p.omit_water });
},
structureTransform(p, s) {
if (p.assembly_name) return StructureSymmetry.buildAssembly(s, '' + p.assembly_name).run();
return StructureSymmetry.builderSymmetryMates(s, p.radius !== void 0 ? p.radius : 5).run();
},
jsonParams: [ AtomSiteTestJsonParam, RadiusParam, OmitWaterParam, AssemblyNameParam ],
restParams: [ ...AtomSiteTestRestParams, RadiusParam, OmitWaterParam, AssemblyNameParam ],
filter: QuerySchemas.interaction
}),
};
export type QueryName = keyof typeof QueryMap

View File

@@ -248,6 +248,7 @@ async function resolveJobEntry(entry: JobEntry, structure: StructureWrapper, enc
if (!entry.copyAllCategories && entry.queryDefinition.filter) encoder.setFilter(entry.queryDefinition.filter);
if (result.length > 0) encode_mmCIF_categories(encoder, result, { copyAllCategories: entry.copyAllCategories });
else ConsoleLogger.logId(entry.job.id, 'Warning', `Empty result for Query ${entry.key}/${entry.queryDefinition.name}`);
if (entry.transform && !Mat4.isIdentity(entry.transform)) GlobalModelTransformInfo.writeMmCif(encoder, entry.transform);
if (!entry.copyAllCategories && entry.queryDefinition.filter) encoder.setFilter();
perf.end('encode');

View File

@@ -4,4 +4,4 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
export const VERSION = '0.9.6';
export const VERSION = '0.9.7';