mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 21:34:23 +08:00
Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ebb0a35fd | ||
|
|
64aaa92d45 | ||
|
|
4baf391efe | ||
|
|
5afdcff6a5 | ||
|
|
339c397860 | ||
|
|
a58cbd31ef | ||
|
|
d232b01cf9 | ||
|
|
ec95270854 | ||
|
|
78cc0d960f | ||
|
|
7f39cf0f37 | ||
|
|
4592510a95 | ||
|
|
46d5442dc5 | ||
|
|
271cff4aba | ||
|
|
94fd5a97d6 | ||
|
|
28678e2f80 | ||
|
|
406307a432 | ||
|
|
56345b5096 | ||
|
|
3fcc42ee0e | ||
|
|
b903677f8a | ||
|
|
ef4b632a07 | ||
|
|
e9d485ca85 | ||
|
|
a149fa5929 | ||
|
|
bb3dde585b | ||
|
|
cd6bbeaa86 | ||
|
|
e3d24dae4b | ||
|
|
687a814a62 | ||
|
|
8f2e99dc51 | ||
|
|
568be030c3 | ||
|
|
97c3ab8b5a | ||
|
|
5db646d139 | ||
|
|
340f8f1af3 | ||
|
|
4484a4452c | ||
|
|
65b654a0a2 | ||
|
|
a8e0c13b0e | ||
|
|
b371f8c11c | ||
|
|
895a13fc0d | ||
|
|
2f9ac711d1 | ||
|
|
b06c134b61 | ||
|
|
3436d03468 | ||
|
|
58df6f3b85 | ||
|
|
ffaf008dce | ||
|
|
eb196a41b5 | ||
|
|
35baaaf594 | ||
|
|
0fc305aaea | ||
|
|
bf67546a61 | ||
|
|
80d54afdd0 | ||
|
|
2a74dfcf46 | ||
|
|
b0f447adde | ||
|
|
b8628ccff1 | ||
|
|
4c30057edf | ||
|
|
53a4826274 | ||
|
|
393fc99ed2 | ||
|
|
7c5dff1c8b | ||
|
|
ce3f13431d | ||
|
|
df54766ab2 | ||
|
|
94233fbcd9 | ||
|
|
d044496eaa | ||
|
|
ca825d720e |
18
.github/workflows/lint.yml
vendored
18
.github/workflows/lint.yml
vendored
@@ -1,18 +0,0 @@
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
eslint:
|
||||
name: eslint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: install node v14
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14
|
||||
- name: yarn install
|
||||
run: yarn install
|
||||
- name: eslint
|
||||
uses: icrawl/action-eslint@v1
|
||||
20
.github/workflows/node.yml
vendored
Normal file
20
.github/workflows/node.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
- run: npm ci
|
||||
- run: sudo apt-get install xvfb
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
- name: Test
|
||||
run: xvfb-run --auto-servernum npm run jest
|
||||
- name: Build
|
||||
run: npm run build
|
||||
36
CHANGELOG.md
36
CHANGELOG.md
@@ -6,6 +6,23 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v2.3.6] - 2021-11-8
|
||||
|
||||
- Add additional measurement controls: orientation (box, axes, ellipsoid) & plane (best fit)
|
||||
- Improve aromatic bond visuals (add ``aromaticScale``, ``aromaticSpacing``, ``aromaticDashCount`` params)
|
||||
- [Breaking] Change ``adjustCylinderLength`` default to ``false`` (set to true for focus representation)
|
||||
- Fix marker highlight color overriding select color
|
||||
- CellPack extension update
|
||||
- add binary model support
|
||||
- add compartment (including membrane) geometry support
|
||||
- add latest mycoplasma model example
|
||||
- Prefer WebGL1 in Safari 15.1.
|
||||
|
||||
## [v2.3.5] - 2021-10-19
|
||||
|
||||
- Fix sequence viewer for PDB files with COMPND record and multichain entities.
|
||||
- Fix index pair bonds order assignment
|
||||
|
||||
## [v2.3.4] - 2021-10-12
|
||||
|
||||
- Fix pickScale not taken into account in line/point shader
|
||||
@@ -148,29 +165,22 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
- Fixed Measurements UI labels (#166)
|
||||
|
||||
## [v2.0.3] - 2021-04-09
|
||||
### Added
|
||||
- Support for ``ColorTheme.palette`` designed for providing gradient-like coloring.
|
||||
|
||||
### Changed
|
||||
- Add support for ``ColorTheme.palette`` designed for providing gradient-like coloring.
|
||||
- [Breaking] The ``zip`` function is now asynchronous and expects a ``RuntimeContext``. Also added ``Zip()`` returning a ``Task``.
|
||||
- [Breaking] Add ``CubeGridFormat`` in ``alpha-orbitals`` extension.
|
||||
|
||||
## [v2.0.2] - 2021-03-29
|
||||
### Added
|
||||
- ``Canvas3D.getRenderObjects``.
|
||||
|
||||
- Add ``Canvas3D.getRenderObjects``.
|
||||
- [WIP] Animate state interpolating, including model trajectories
|
||||
|
||||
### Changed
|
||||
- Recognise MSE, SEP, TPO, PTR and PCA as non-standard amino-acids.
|
||||
|
||||
### Fixed
|
||||
- VolumeFromDensityServerCif transform label
|
||||
|
||||
- Fix VolumeFromDensityServerCif transform label
|
||||
|
||||
## [v2.0.1] - 2021-03-23
|
||||
### Fixed
|
||||
|
||||
- Exclude tsconfig.commonjs.tsbuildinfo from npm bundle
|
||||
|
||||
|
||||
## [v2.0.0] - 2021-03-23
|
||||
|
||||
Too many changes to list as this is the start of the changelog... Notably, default exports are now forbidden.
|
||||
|
||||
@@ -122,9 +122,9 @@ and navigate to `build/viewer`
|
||||
|
||||
**Convert any CIF to BinaryCIF**
|
||||
|
||||
node lib/servers/model/preprocess -i file.cif -ob file.bcif
|
||||
node lib/commonjs/servers/model/preprocess -i file.cif -ob file.bcif
|
||||
|
||||
To see all available commands, use ``node lib/servers/model/preprocess -h``.
|
||||
To see all available commands, use ``node lib/commonjs/servers/model/preprocess -h``.
|
||||
|
||||
Or
|
||||
|
||||
|
||||
18800
package-lock.json
generated
18800
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "2.3.4",
|
||||
"version": "2.3.6",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -96,6 +96,7 @@
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.1.4",
|
||||
"@graphql-codegen/typescript-operations": "^2.1.6",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/gl": "^4.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.32.0",
|
||||
"@typescript-eslint/parser": "^4.32.0",
|
||||
"benchmark": "^2.1.4",
|
||||
@@ -151,5 +152,8 @@
|
||||
"tslib": "^2.3.1",
|
||||
"util.promisify": "^1.1.1",
|
||||
"xhr2": "^0.2.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"gl": "^4.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Ludovic Autin <autin@scripps.edu>
|
||||
* @author Ludovic Autin <ludovic.autin@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
|
||||
@@ -13,16 +13,27 @@ export interface CellPack {
|
||||
|
||||
export interface CellPacking {
|
||||
name: string,
|
||||
location: 'surface' | 'interior' | 'cytoplasme',
|
||||
location: 'surface' | 'interior' | 'cytoplasme'
|
||||
ingredients: Packing['ingredients']
|
||||
compartment?: CellCompartment
|
||||
}
|
||||
|
||||
//
|
||||
export interface CellCompartment {
|
||||
filename?: string
|
||||
geom_type?: 'raw' | 'file' | 'sphere' | 'mb' | 'None'
|
||||
compartment_primitives?: CompartmentPrimitives
|
||||
}
|
||||
|
||||
export interface Cell {
|
||||
recipe: Recipe
|
||||
options?: RecipeOptions
|
||||
cytoplasme?: Packing
|
||||
compartments?: { [key: string]: Compartment }
|
||||
mapping_ids?: { [key: number]: [number, string] }
|
||||
}
|
||||
|
||||
export interface RecipeOptions {
|
||||
resultfile?: string
|
||||
}
|
||||
|
||||
export interface Recipe {
|
||||
@@ -35,8 +46,29 @@ export interface Recipe {
|
||||
export interface Compartment {
|
||||
surface?: Packing
|
||||
interior?: Packing
|
||||
geom?: unknown
|
||||
geom_type?: 'raw' | 'file' | 'sphere' | 'mb' | 'None'
|
||||
mb?: CompartmentPrimitives
|
||||
}
|
||||
|
||||
// Primitives discribing a compartment
|
||||
export const enum CompartmentPrimitiveType {
|
||||
MetaBall = 0,
|
||||
Sphere = 1,
|
||||
Cube = 2,
|
||||
Cylinder = 3,
|
||||
Cone = 4,
|
||||
Plane = 5,
|
||||
None = 6
|
||||
}
|
||||
|
||||
export interface CompartmentPrimitives{
|
||||
positions?: number[];
|
||||
radii?: number[];
|
||||
types?: CompartmentPrimitiveType[];
|
||||
}
|
||||
|
||||
|
||||
export interface Packing {
|
||||
ingredients: { [key: string]: Ingredient }
|
||||
}
|
||||
@@ -64,11 +96,13 @@ export interface Ingredient {
|
||||
[curveX: string]: unknown;
|
||||
/** the orientation in the membrane */
|
||||
principalAxis?: Vec3;
|
||||
principalVector?: Vec3;
|
||||
/** offset along membrane */
|
||||
offset?: Vec3;
|
||||
ingtype?: string;
|
||||
color?: Vec3;
|
||||
confidence?: number;
|
||||
Type?: string;
|
||||
}
|
||||
|
||||
export interface IngredientSource {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Ludovic Autin <ludovic.autin@gmail.com>
|
||||
*/
|
||||
|
||||
import { StateAction, StateBuilder, StateTransformer, State } from '../../mol-state';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginStateObject as PSO } from '../../mol-plugin-state/objects';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Ingredient, IngredientSource, CellPacking } from './data';
|
||||
import { Ingredient, CellPacking, CompartmentPrimitives } from './data';
|
||||
import { getFromPdb, getFromCellPackDB, IngredientFiles, parseCif, parsePDBfile, getStructureMean, getFromOPM } from './util';
|
||||
import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit, Trajectory } from '../../mol-model/structure';
|
||||
import { trajectoryFromMmCIF, MmcifFormat } from '../../mol-model-formats/structure/mmcif';
|
||||
@@ -17,7 +18,7 @@ import { Mat4, Vec3, Quat } from '../../mol-math/linear-algebra';
|
||||
import { SymmetryOperator } from '../../mol-math/geometry';
|
||||
import { Task, RuntimeContext } from '../../mol-task';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl, StructureFromAssemblies } from './state';
|
||||
import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl, StructureFromAssemblies, CreateCompartmentSphere } from './state';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { getMatFromResamplePoints } from './curve';
|
||||
import { compile } from '../../mol-script/runtime/query/compiler';
|
||||
@@ -28,8 +29,9 @@ import { createModels } from '../../mol-model-formats/structure/basic/parser';
|
||||
import { CellpackPackingPreset, CellpackMembranePreset } from './preset';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { readFromFile } from '../../mol-util/data-source';
|
||||
import { objectForEach } from '../../mol-util/object';
|
||||
import { readFromFile } from '../../mol-util/data-source';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
|
||||
function getCellPackModelUrl(fileName: string, baseUrl: string) {
|
||||
return `${baseUrl}/results/${fileName}`;
|
||||
@@ -41,10 +43,14 @@ class TrajectoryCache {
|
||||
get(id: string) { return this.map.get(id); }
|
||||
}
|
||||
|
||||
async function getModel(plugin: PluginContext, id: string, ingredient: Ingredient, baseUrl: string, trajCache: TrajectoryCache, file?: Asset.File) {
|
||||
async function getModel(plugin: PluginContext, id: string, ingredient: Ingredient,
|
||||
baseUrl: string, trajCache: TrajectoryCache, location: string,
|
||||
file?: Asset.File
|
||||
) {
|
||||
const assetManager = plugin.managers.asset;
|
||||
const modelIndex = (ingredient.source.model) ? parseInt(ingredient.source.model) : 0;
|
||||
const surface = (ingredient.ingtype) ? (ingredient.ingtype === 'transmembrane') : false;
|
||||
let surface = (ingredient.ingtype) ? (ingredient.ingtype === 'transmembrane') : false;
|
||||
if (location === 'surface') surface = true;
|
||||
let trajectory = trajCache.get(id);
|
||||
const assets: Asset.Wrapper[] = [];
|
||||
if (!trajectory) {
|
||||
@@ -72,6 +78,7 @@ async function getModel(plugin: PluginContext, id: string, ingredient: Ingredien
|
||||
try {
|
||||
const data = await getFromOPM(plugin, id, assetManager);
|
||||
assets.push(data.asset);
|
||||
data.pdb.id! = id.toUpperCase();
|
||||
trajectory = await plugin.runTask(trajectoryFromPDB(data.pdb));
|
||||
} catch (e) {
|
||||
// fallback to getFromPdb
|
||||
@@ -100,7 +107,7 @@ async function getModel(plugin: PluginContext, id: string, ingredient: Ingredien
|
||||
return { model, assets };
|
||||
}
|
||||
|
||||
async function getStructure(plugin: PluginContext, model: Model, source: IngredientSource, props: { assembly?: string } = {}) {
|
||||
async function getStructure(plugin: PluginContext, model: Model, source: Ingredient, props: { assembly?: string } = {}) {
|
||||
let structure = Structure.ofModel(model);
|
||||
const { assembly } = props;
|
||||
|
||||
@@ -108,11 +115,12 @@ async function getStructure(plugin: PluginContext, model: Model, source: Ingredi
|
||||
structure = await plugin.runTask(StructureSymmetry.buildAssembly(structure, assembly));
|
||||
}
|
||||
let query;
|
||||
if (source.selection) {
|
||||
const asymIds: string[] = source.selection.replace(' ', '').replace(':', '').split('or');
|
||||
if (source.source.selection) {
|
||||
const sel = source.source.selection;
|
||||
// selection can have the model ID as well. remove it
|
||||
const asymIds: string[] = sel.replace(/ /g, '').replace(/:/g, '').split('or').slice(1);
|
||||
query = MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
|
||||
'chain-test': MS.core.set.has([MS.set(...asymIds), MS.ammp('auth_asym_id')])
|
||||
})
|
||||
]);
|
||||
@@ -123,11 +131,11 @@ async function getStructure(plugin: PluginContext, model: Model, source: Ingredi
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
const compiled = compile<StructureSelection>(query);
|
||||
const result = compiled(new QueryContext(structure));
|
||||
structure = StructureSelection.unionStructure(result);
|
||||
|
||||
// change here if possible the label ?
|
||||
// structure.label = source.name;
|
||||
return structure;
|
||||
}
|
||||
|
||||
@@ -141,9 +149,9 @@ function getTransformLegacy(trans: Vec3, rot: Quat) {
|
||||
}
|
||||
|
||||
function getTransform(trans: Vec3, rot: Quat) {
|
||||
const q: Quat = Quat.create(rot[0], rot[1], rot[2], rot[3]);
|
||||
const q: Quat = Quat.create(-rot[0], rot[1], rot[2], -rot[3]);
|
||||
const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q);
|
||||
const p: Vec3 = Vec3.create(trans[0], trans[1], trans[2]);
|
||||
const p: Vec3 = Vec3.create(-trans[0], trans[1], trans[2]);
|
||||
Mat4.setTranslation(m, p);
|
||||
return m;
|
||||
}
|
||||
@@ -168,7 +176,7 @@ function getCurveTransforms(ingredient: Ingredient) {
|
||||
for (let i = 0; i < n; ++i) {
|
||||
const cname = `curve${i}`;
|
||||
if (!(cname in ingredient)) {
|
||||
// console.warn(`Expected '${cname}' in ingredient`)
|
||||
console.warn(`Expected '${cname}' in ingredient`);
|
||||
continue;
|
||||
}
|
||||
const _points = ingredient[cname] as Vec3[];
|
||||
@@ -179,7 +187,7 @@ function getCurveTransforms(ingredient: Ingredient) {
|
||||
// test for resampling
|
||||
const distance: number = Vec3.distance(_points[0], _points[1]);
|
||||
if (distance >= segmentLength + 2.0) {
|
||||
console.info(distance);
|
||||
// console.info(distance);
|
||||
resampling = true;
|
||||
}
|
||||
const points = new Float32Array(_points.length * 3);
|
||||
@@ -190,8 +198,8 @@ function getCurveTransforms(ingredient: Ingredient) {
|
||||
return instances;
|
||||
}
|
||||
|
||||
function getAssembly(transforms: Mat4[], structure: Structure) {
|
||||
const builder = Structure.Builder();
|
||||
function getAssembly(name: string, transforms: Mat4[], structure: Structure) {
|
||||
const builder = Structure.Builder({ label: name });
|
||||
const { units } = structure;
|
||||
|
||||
for (let i = 0, il = transforms.length; i < il; ++i) {
|
||||
@@ -307,13 +315,13 @@ async function getCurve(plugin: PluginContext, name: string, ingredient: Ingredi
|
||||
});
|
||||
|
||||
const curveModel = await plugin.runTask(curveModelTask);
|
||||
return getStructure(plugin, curveModel, ingredient.source);
|
||||
// ingredient.source.selection = undefined;
|
||||
return getStructure(plugin, curveModel, ingredient);
|
||||
}
|
||||
|
||||
async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles, trajCache: TrajectoryCache) {
|
||||
async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles, trajCache: TrajectoryCache, location: 'surface' | 'interior' | 'cytoplasme') {
|
||||
const { name, source, results, nbCurve } = ingredient;
|
||||
if (source.pdb === 'None') return;
|
||||
|
||||
const file = ingredientFiles[source.pdb];
|
||||
if (!file) {
|
||||
// TODO can these be added to the library?
|
||||
@@ -325,13 +333,13 @@ async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredi
|
||||
}
|
||||
|
||||
// model id in case structure is NMR
|
||||
const { model, assets } = await getModel(plugin, source.pdb || name, ingredient, baseUrl, trajCache, file);
|
||||
const { model, assets } = await getModel(plugin, source.pdb || name, ingredient, baseUrl, trajCache, location, file);
|
||||
if (!model) return;
|
||||
|
||||
let structure: Structure;
|
||||
if (nbCurve) {
|
||||
structure = await getCurve(plugin, name, ingredient, getCurveTransforms(ingredient), model);
|
||||
} else {
|
||||
if ((!results || results.length === 0)) return;
|
||||
let bu: string|undefined = source.bu ? source.bu : undefined;
|
||||
if (bu) {
|
||||
if (bu === 'AU') {
|
||||
@@ -340,10 +348,11 @@ async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredi
|
||||
bu = bu.slice(2);
|
||||
}
|
||||
}
|
||||
structure = await getStructure(plugin, model, source, { assembly: bu });
|
||||
structure = await getStructure(plugin, model, ingredient, { assembly: bu });
|
||||
// transform with offset and pcp
|
||||
let legacy: boolean = true;
|
||||
if (ingredient.offset || ingredient.principalAxis) {
|
||||
const pcp = ingredient.principalVector ? ingredient.principalVector : ingredient.principalAxis;
|
||||
if (pcp) {
|
||||
legacy = false;
|
||||
const structureMean = getStructureMean(structure);
|
||||
Vec3.negate(structureMean, structureMean);
|
||||
@@ -351,38 +360,44 @@ async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredi
|
||||
Mat4.setTranslation(m1, structureMean);
|
||||
structure = Structure.transform(structure, m1);
|
||||
if (ingredient.offset) {
|
||||
if (!Vec3.exactEquals(ingredient.offset, Vec3.zero())) {
|
||||
const o: Vec3 = Vec3.create(ingredient.offset[0], ingredient.offset[1], ingredient.offset[2]);
|
||||
if (!Vec3.exactEquals(o, Vec3.zero())) { // -1, 1, 4e-16 ??
|
||||
if (location !== 'surface') {
|
||||
Vec3.negate(o, o);
|
||||
}
|
||||
const m: Mat4 = Mat4.identity();
|
||||
Mat4.setTranslation(m, ingredient.offset);
|
||||
Mat4.setTranslation(m, o);
|
||||
structure = Structure.transform(structure, m);
|
||||
}
|
||||
}
|
||||
if (ingredient.principalAxis) {
|
||||
if (!Vec3.exactEquals(ingredient.principalAxis, Vec3.unitZ)) {
|
||||
if (pcp) {
|
||||
const p: Vec3 = Vec3.create(pcp[0], pcp[1], pcp[2]);
|
||||
if (!Vec3.exactEquals(p, Vec3.unitZ)) {
|
||||
const q: Quat = Quat.identity();
|
||||
Quat.rotationTo(q, ingredient.principalAxis, Vec3.unitZ);
|
||||
Quat.rotationTo(q, p, Vec3.unitZ);
|
||||
const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q);
|
||||
structure = Structure.transform(structure, m);
|
||||
}
|
||||
}
|
||||
}
|
||||
structure = getAssembly(getResultTransforms(results, legacy), structure);
|
||||
|
||||
structure = getAssembly(name, getResultTransforms(results, legacy), structure);
|
||||
}
|
||||
|
||||
return { structure, assets };
|
||||
}
|
||||
|
||||
|
||||
export function createStructureFromCellPack(plugin: PluginContext, packing: CellPacking, baseUrl: string, ingredientFiles: IngredientFiles) {
|
||||
return Task.create('Create Packing Structure', async ctx => {
|
||||
const { ingredients, name } = packing;
|
||||
const { ingredients, location, name } = packing;
|
||||
const assets: Asset.Wrapper[] = [];
|
||||
const trajCache = new TrajectoryCache();
|
||||
const structures: Structure[] = [];
|
||||
const colors: Color[] = [];
|
||||
let skipColors: boolean = false;
|
||||
for (const iName in ingredients) {
|
||||
if (ctx.shouldUpdate) await ctx.update(iName);
|
||||
const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles, trajCache);
|
||||
const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles, trajCache, location);
|
||||
if (ingredientStructure) {
|
||||
structures.push(ingredientStructure.structure);
|
||||
assets.push(...ingredientStructure.assets);
|
||||
@@ -390,7 +405,7 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell
|
||||
if (c) {
|
||||
colors.push(Color.fromNormalizedRgb(c[0], c[1], c[2]));
|
||||
} else {
|
||||
skipColors = true;
|
||||
colors.push(Color.fromNormalizedRgb(1, 0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -414,21 +429,20 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell
|
||||
}
|
||||
|
||||
if (ctx.shouldUpdate) await ctx.update(`${name} - structure`);
|
||||
const structure = Structure.create(units);
|
||||
const structure = Structure.create(units, { label: name + '.' + location });
|
||||
for (let i = 0, il = structure.models.length; i < il; ++i) {
|
||||
Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
|
||||
}
|
||||
return { structure, assets, colors: skipColors ? undefined : colors };
|
||||
return { structure, assets, colors: colors };
|
||||
});
|
||||
}
|
||||
|
||||
async function handleHivRna(plugin: PluginContext, packings: CellPacking[], baseUrl: string) {
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0') {
|
||||
if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0' || packings[i].name === 'HIV_capsid') {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, `${baseUrl}/extras/rna_allpoints.json`);
|
||||
const json = await plugin.runTask(plugin.managers.asset.resolve(url, 'json', false));
|
||||
const points = json.data.points as number[];
|
||||
|
||||
const curve0: Vec3[] = [];
|
||||
for (let j = 0, jl = points.length; j < jl; j += 3) {
|
||||
curve0.push(Vec3.fromArray(Vec3(), points, j));
|
||||
@@ -465,7 +479,8 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let legacy_membrane: boolean = false; // temporary variable until all membrane are converted to the new correct cif format
|
||||
let geometry_membrane: boolean = false; // membrane can be a mesh geometry
|
||||
let b = state.build().toRoot();
|
||||
if (file) {
|
||||
if (file.name.endsWith('.cif')) {
|
||||
@@ -474,27 +489,82 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
|
||||
b = b.apply(StateTransforms.Data.ReadFile, { file, isBinary: true, label: file.name }, { state: { isGhost: true } });
|
||||
}
|
||||
} else {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}.bcif`);
|
||||
b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
|
||||
if (name.toLowerCase().endsWith('.bcif')) {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}`);
|
||||
b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
|
||||
} else if (name.toLowerCase().endsWith('.cif')) {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}`);
|
||||
b = b.apply(StateTransforms.Data.Download, { url, isBinary: false, label: name }, { state: { isGhost: true } });
|
||||
} else if (name.toLowerCase().endsWith('.ply')) {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/geometries/${name}`);
|
||||
b = b.apply(StateTransforms.Data.Download, { url, isBinary: false, label: name }, { state: { isGhost: true } });
|
||||
geometry_membrane = true;
|
||||
} else {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}.bcif`);
|
||||
b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
|
||||
legacy_membrane = true;
|
||||
}
|
||||
}
|
||||
|
||||
const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
|
||||
.apply(StructureFromAssemblies, undefined, { state: { isGhost: true } })
|
||||
.commit({ revertOnError: true });
|
||||
|
||||
const membraneParams = {
|
||||
representation: params.preset.representation,
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: '1' }
|
||||
}
|
||||
};
|
||||
if (legacy_membrane) {
|
||||
// old membrane
|
||||
const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
|
||||
.apply(StructureFromAssemblies, undefined, { state: { isGhost: true } })
|
||||
.commit({ revertOnError: true });
|
||||
const membraneParams = {
|
||||
representation: params.preset.representation,
|
||||
};
|
||||
await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
|
||||
} else if (geometry_membrane) {
|
||||
await b.apply(StateTransforms.Data.ParsePly, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.ShapeFromPly)
|
||||
.apply(StateTransforms.Representation.ShapeRepresentation3D, { xrayShaded: true,
|
||||
doubleSided: true, coloring: { name: 'uniform', params: { color: ColorNames.orange } } })
|
||||
.commit({ revertOnError: true });
|
||||
} else {
|
||||
const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.StructureFromModel, props, { state: { isGhost: true } })
|
||||
.commit({ revertOnError: true });
|
||||
const membraneParams = {
|
||||
representation: params.preset.representation,
|
||||
};
|
||||
await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
|
||||
}
|
||||
}
|
||||
|
||||
await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
|
||||
async function handleMembraneSpheres(state: State, primitives: CompartmentPrimitives) {
|
||||
const nSpheres = primitives.positions!.length / 3;
|
||||
// console.log('ok mb ', nSpheres);
|
||||
// TODO : take in account the type of the primitives.
|
||||
for (let j = 0; j < nSpheres; j++) {
|
||||
await state.build()
|
||||
.toRoot()
|
||||
.apply(CreateCompartmentSphere, {
|
||||
center: Vec3.create(
|
||||
primitives.positions![j * 3 + 0],
|
||||
primitives.positions![j * 3 + 1],
|
||||
primitives.positions![j * 3 + 2]
|
||||
),
|
||||
radius: primitives!.radii![j]
|
||||
})
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) {
|
||||
const ingredientFiles = params.ingredients || [];
|
||||
|
||||
let cellPackJson: StateBuilder.To<PSO.Format.Json, StateTransformer<PSO.Data.String, PSO.Format.Json>>;
|
||||
let resultsFile: Asset.File | null = params.results;
|
||||
if (params.source.name === 'id') {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, getCellPackModelUrl(params.source.params, params.baseUrl));
|
||||
cellPackJson = state.build().toRoot()
|
||||
@@ -506,29 +576,36 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat
|
||||
return;
|
||||
}
|
||||
|
||||
let jsonFile: Asset.File;
|
||||
let modelFile: Asset.File;
|
||||
if (file.name.toLowerCase().endsWith('.zip')) {
|
||||
const data = await readFromFile(file.file, 'zip').runInContext(runtime);
|
||||
jsonFile = Asset.File(new File([data['model.json']], 'model.json'));
|
||||
if (data['model.json']) {
|
||||
modelFile = Asset.File(new File([data['model.json']], 'model.json'));
|
||||
} else {
|
||||
throw new Error('model.json missing from zip file');
|
||||
}
|
||||
if (data['results.bin']) {
|
||||
resultsFile = Asset.File(new File([data['results.bin']], 'results.bin'));
|
||||
}
|
||||
objectForEach(data, (v, k) => {
|
||||
if (k === 'model.json') return;
|
||||
if (k === 'results.bin') return;
|
||||
ingredientFiles.push(Asset.File(new File([v], k)));
|
||||
});
|
||||
} else {
|
||||
jsonFile = file;
|
||||
modelFile = file;
|
||||
}
|
||||
|
||||
cellPackJson = state.build().toRoot()
|
||||
.apply(StateTransforms.Data.ReadFile, { file: jsonFile, isBinary: false, label: jsonFile.name }, { state: { isGhost: true } });
|
||||
.apply(StateTransforms.Data.ReadFile, { file: modelFile, isBinary: false, label: modelFile.name }, { state: { isGhost: true } });
|
||||
}
|
||||
|
||||
const cellPackBuilder = cellPackJson
|
||||
.apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
|
||||
.apply(ParseCellPack);
|
||||
.apply(ParseCellPack, { resultsFile, baseUrl: params.baseUrl });
|
||||
|
||||
const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(runtime);
|
||||
const { packings } = cellPackObject.obj!.data;
|
||||
|
||||
const { packings } = cellPackObject.obj!.data;
|
||||
await handleHivRna(plugin, packings, params.baseUrl);
|
||||
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
@@ -544,8 +621,30 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat
|
||||
representation: params.preset.representation,
|
||||
};
|
||||
await CellpackPackingPreset.apply(packing, packingParams, plugin);
|
||||
if (packings[i].location === 'surface' && params.membrane) {
|
||||
await loadMembrane(plugin, packings[i].name, state, params);
|
||||
if (packings[i].compartment) {
|
||||
if (params.membrane === 'lipids') {
|
||||
if (packings[i].compartment!.geom_type) {
|
||||
if (packings[i].compartment!.geom_type === 'file') {
|
||||
// TODO: load mesh files or vertex,faces data
|
||||
await loadMembrane(plugin, packings[i].compartment!.filename!, state, params);
|
||||
} else if (packings[i].compartment!.compartment_primitives) {
|
||||
await handleMembraneSpheres(state, packings[i].compartment!.compartment_primitives!);
|
||||
}
|
||||
} else {
|
||||
// try loading membrane from repo as a bcif file or from the given list of files.
|
||||
if (params.membrane === 'lipids') {
|
||||
await loadMembrane(plugin, packings[i].name, state, params);
|
||||
}
|
||||
}
|
||||
} else if (params.membrane === 'geometry') {
|
||||
if (packings[i].compartment!.compartment_primitives) {
|
||||
await handleMembraneSpheres(state, packings[i].compartment!.compartment_primitives!);
|
||||
} else if (packings[i].compartment!.geom_type === 'file') {
|
||||
if (packings[i].compartment!.filename!.toLowerCase().endsWith('.ply')) {
|
||||
await loadMembrane(plugin, packings[i].compartment!.filename!, state, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -555,19 +654,19 @@ const LoadCellPackModelParams = {
|
||||
'id': PD.Select('InfluenzaModel2.json', [
|
||||
['blood_hiv_immature_inside.json', 'Blood HIV immature'],
|
||||
['HIV_immature_model.json', 'HIV immature'],
|
||||
['BloodHIV1.0_mixed_fixed_nc1.cpr', 'Blood HIV'],
|
||||
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV'],
|
||||
['Blood_HIV.json', 'Blood HIV'],
|
||||
['HIV-1_0.1.6-8_mixed_radii_pdb.json', 'HIV'],
|
||||
['influenza_model1.json', 'Influenza envelope'],
|
||||
['InfluenzaModel2.json', 'Influenza Complete'],
|
||||
['InfluenzaModel2.json', 'Influenza complete'],
|
||||
['ExosomeModel.json', 'Exosome Model'],
|
||||
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma simple'],
|
||||
['MycoplasmaModel.json', 'Mycoplasma WholeCell model'],
|
||||
['MycoplasmaGenitalium.json', 'Mycoplasma Genitalium curated model'],
|
||||
] as const, { description: 'Download the model definition with `id` from the server at `baseUrl.`' }),
|
||||
'file': PD.File({ accept: '.json,.cpr,.zip', description: 'Open model definition from .json/.cpr file or open .zip file containing model definition plus ingredients.' }),
|
||||
'file': PD.File({ accept: '.json,.cpr,.zip', description: 'Open model definition from .json/.cpr file or open .zip file containing model definition plus ingredients.', label: 'Recipe file' }),
|
||||
}, { options: [['id', 'Id'], ['file', 'File']] }),
|
||||
baseUrl: PD.Text(DefaultCellPackBaseUrl),
|
||||
membrane: PD.Boolean(true),
|
||||
ingredients: PD.FileList({ accept: '.cif,.bcif,.pdb', label: 'Ingredients' }),
|
||||
results: PD.File({ accept: '.bin', description: 'open results file in binary format from cellpackgpu for the specified recipe', label: 'Results file' }),
|
||||
membrane: PD.Select('lipids', PD.arrayToOptions(['lipids', 'geometry', 'none'])),
|
||||
ingredients: PD.FileList({ accept: '.cif,.bcif,.pdb', label: 'Ingredient files' }),
|
||||
preset: PD.Group({
|
||||
traceOnly: PD.Boolean(false),
|
||||
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'orientation']))
|
||||
@@ -581,4 +680,4 @@ export const LoadCellPackModel = StateAction.build({
|
||||
from: PSO.Root
|
||||
})(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
|
||||
await loadPackings(ctx, taskCtx, state, params);
|
||||
}));
|
||||
}));
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Ludovic Autin <ludovic.autin@gmail.com>
|
||||
*/
|
||||
|
||||
import { StateObjectRef } from '../../mol-state';
|
||||
@@ -9,8 +10,6 @@ import { StructureRepresentationPresetProvider, presetStaticComponent } from '..
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { CellPackGenerateColorThemeProvider } from './color/generate';
|
||||
import { CellPackInfoProvider } from './property';
|
||||
import { CellPackProvidedColorThemeProvider } from './color/provided';
|
||||
|
||||
export const CellpackPackingPresetParams = {
|
||||
traceOnly: PD.Boolean(true),
|
||||
@@ -42,8 +41,8 @@ export const CellpackPackingPreset = StructureRepresentationPresetProvider({
|
||||
Object.assign(reprProps, { sizeFactor: 2 });
|
||||
}
|
||||
|
||||
const info = structureCell.obj?.data && CellPackInfoProvider.get(structureCell.obj?.data).value;
|
||||
const color = info?.colors ? CellPackProvidedColorThemeProvider.name : CellPackGenerateColorThemeProvider.name;
|
||||
// default is generated
|
||||
const color = CellPackGenerateColorThemeProvider.name;
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, {});
|
||||
const representations = {
|
||||
@@ -92,4 +91,4 @@ export const CellpackMembranePreset = StructureRepresentationPresetProvider({
|
||||
|
||||
return { components, representations };
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -34,4 +34,4 @@ export const CellPackInfoProvider: CustomStructureProperty.Provider<typeof CellP
|
||||
value: { ...CellPackInfoParams.info.defaultValue, ...props.info }
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
70
src/extensions/cellpack/representation.ts
Normal file
70
src/extensions/cellpack/representation.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Ludovic Autin <ludovic.autin@gmail.com>
|
||||
*/
|
||||
|
||||
import { ShapeRepresentation } from '../../mol-repr/shape/representation';
|
||||
import { Shape } from '../../mol-model/shape';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
||||
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
|
||||
// import { Polyhedron, DefaultPolyhedronProps } from '../../mol-geo/primitive/polyhedron';
|
||||
// import { Icosahedron } from '../../mol-geo/primitive/icosahedron';
|
||||
import { Sphere } from '../../mol-geo/primitive/sphere';
|
||||
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { RepresentationParamsGetter, Representation, RepresentationContext } from '../../mol-repr/representation';
|
||||
|
||||
|
||||
interface MembraneSphereData {
|
||||
radius: number
|
||||
center: Vec3
|
||||
}
|
||||
|
||||
|
||||
const MembraneSphereParams = {
|
||||
...Mesh.Params,
|
||||
cellColor: PD.Color(ColorNames.orange),
|
||||
cellScale: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }),
|
||||
radius: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }),
|
||||
center: PD.Vec3(Vec3.create(0, 0, 0)),
|
||||
quality: { ...Mesh.Params.quality, isEssential: false },
|
||||
};
|
||||
|
||||
type MeshParams = typeof MembraneSphereParams
|
||||
|
||||
const MembraneSphereVisuals = {
|
||||
'mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneSphereData, MeshParams>) => ShapeRepresentation(getMBShape, Mesh.Utils),
|
||||
};
|
||||
|
||||
export const MBParams = {
|
||||
...MembraneSphereParams
|
||||
};
|
||||
export type MBParams = typeof MBParams
|
||||
export type UnitcellProps = PD.Values<MBParams>
|
||||
|
||||
function getMBMesh(data: MembraneSphereData, props: UnitcellProps, mesh?: Mesh) {
|
||||
const state = MeshBuilder.createState(256, 128, mesh);
|
||||
const radius = props.radius;
|
||||
const asphere = Sphere(3);
|
||||
const trans: Mat4 = Mat4.identity();
|
||||
Mat4.fromScaling(trans, Vec3.create(radius, radius, radius));
|
||||
state.currentGroup = 1;
|
||||
MeshBuilder.addPrimitive(state, trans, asphere);
|
||||
const m = MeshBuilder.getMesh(state);
|
||||
return m;
|
||||
}
|
||||
|
||||
function getMBShape(ctx: RuntimeContext, data: MembraneSphereData, props: UnitcellProps, shape?: Shape<Mesh>) {
|
||||
const geo = getMBMesh(data, props, shape && shape.geometry);
|
||||
const label = 'mb';
|
||||
return Shape.create(label, data, geo, () => props.cellColor, () => 1, () => label);
|
||||
}
|
||||
|
||||
export type MBRepresentation = Representation<MembraneSphereData, MBParams>
|
||||
export function MBRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneSphereData, MBParams>): MBRepresentation {
|
||||
return Representation.createMulti('MB', ctx, getParams, Representation.StateBuilder, MembraneSphereVisuals as unknown as Representation.Def<MembraneSphereData, MBParams>);
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Ludovic Autin <ludovic.autin@gmail.com>
|
||||
*/
|
||||
|
||||
import { PluginStateObject as PSO, PluginStateTransform } from '../../mol-plugin-state/objects';
|
||||
@@ -15,9 +16,13 @@ import { PluginContext } from '../../mol-plugin/context';
|
||||
import { CellPackInfoProvider } from './property';
|
||||
import { Structure, StructureSymmetry, Unit, Model } from '../../mol-model/structure';
|
||||
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
|
||||
import { Vec3, Quat } from '../../mol-math/linear-algebra';
|
||||
import { StateTransformer } from '../../mol-state';
|
||||
import { MBRepresentation, MBParams } from './representation';
|
||||
import { IsNativeEndianLittle, flipByteOrder } from '../../mol-io/common/binary';
|
||||
import { getFloatValue } from './util';
|
||||
|
||||
export const DefaultCellPackBaseUrl = 'https://mesoscope.scripps.edu/data/cellPACK_data/cellPACK_database_1.1.0/';
|
||||
|
||||
export const DefaultCellPackBaseUrl = 'https://raw.githubusercontent.com/mesoscope/cellPACK_data/master/cellPACK_database_1.1.0';
|
||||
export class CellPack extends PSO.Create<_CellPack>({ name: 'CellPack', typeClass: 'Object' }) { }
|
||||
|
||||
export { ParseCellPack };
|
||||
@@ -26,26 +31,173 @@ const ParseCellPack = PluginStateTransform.BuiltIn({
|
||||
name: 'parse-cellpack',
|
||||
display: { name: 'Parse CellPack', description: 'Parse CellPack from JSON data' },
|
||||
from: PSO.Format.Json,
|
||||
to: CellPack
|
||||
to: CellPack,
|
||||
params: a => {
|
||||
return {
|
||||
resultsFile: PD.File({ accept: '.bin' }),
|
||||
baseUrl: PD.Text(DefaultCellPackBaseUrl)
|
||||
};
|
||||
}
|
||||
})({
|
||||
apply({ a }) {
|
||||
apply({ a, params, cache }, plugin: PluginContext) {
|
||||
return Task.create('Parse CellPack', async ctx => {
|
||||
const cell = a.data as Cell;
|
||||
|
||||
let counter_id = 0;
|
||||
let fiber_counter_id = 0;
|
||||
let comp_counter = 0;
|
||||
const packings: CellPacking[] = [];
|
||||
const { compartments, cytoplasme } = cell;
|
||||
if (!cell.mapping_ids) cell.mapping_ids = {};
|
||||
if (cytoplasme) {
|
||||
packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients });
|
||||
for (const iName in cytoplasme.ingredients) {
|
||||
if (cytoplasme.ingredients[iName].ingtype === 'fiber') {
|
||||
cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName];
|
||||
if (!cytoplasme.ingredients[iName].nbCurve) cytoplasme.ingredients[iName].nbCurve = 0;
|
||||
fiber_counter_id++;
|
||||
} else {
|
||||
cell.mapping_ids[counter_id] = [comp_counter, iName];
|
||||
if (!cytoplasme.ingredients[iName].results) { cytoplasme.ingredients[iName].results = []; }
|
||||
counter_id++;
|
||||
}
|
||||
}
|
||||
comp_counter++;
|
||||
}
|
||||
if (compartments) {
|
||||
for (const name in compartments) {
|
||||
const { surface, interior } = compartments[name];
|
||||
if (surface) packings.push({ name, location: 'surface', ingredients: surface.ingredients });
|
||||
if (interior) packings.push({ name, location: 'interior', ingredients: interior.ingredients });
|
||||
let filename = '';
|
||||
if (compartments[name].geom_type === 'file') {
|
||||
filename = (compartments[name].geom) ? compartments[name].geom as string : '';
|
||||
}
|
||||
const compartment = { filename: filename, geom_type: compartments[name].geom_type, compartment_primitives: compartments[name].mb };
|
||||
if (surface) {
|
||||
packings.push({ name, location: 'surface', ingredients: surface.ingredients, compartment: compartment });
|
||||
for (const iName in surface.ingredients) {
|
||||
if (surface.ingredients[iName].ingtype === 'fiber') {
|
||||
cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName];
|
||||
if (!surface.ingredients[iName].nbCurve) surface.ingredients[iName].nbCurve = 0;
|
||||
fiber_counter_id++;
|
||||
} else {
|
||||
cell.mapping_ids[counter_id] = [comp_counter, iName];
|
||||
if (!surface.ingredients[iName].results) { surface.ingredients[iName].results = []; }
|
||||
counter_id++;
|
||||
}
|
||||
}
|
||||
comp_counter++;
|
||||
}
|
||||
if (interior) {
|
||||
if (!surface) packings.push({ name, location: 'interior', ingredients: interior.ingredients, compartment: compartment });
|
||||
else packings.push({ name, location: 'interior', ingredients: interior.ingredients });
|
||||
for (const iName in interior.ingredients) {
|
||||
if (interior.ingredients[iName].ingtype === 'fiber') {
|
||||
cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName];
|
||||
if (!interior.ingredients[iName].nbCurve) interior.ingredients[iName].nbCurve = 0;
|
||||
fiber_counter_id++;
|
||||
} else {
|
||||
cell.mapping_ids[counter_id] = [comp_counter, iName];
|
||||
if (!interior.ingredients[iName].results) { interior.ingredients[iName].results = []; }
|
||||
counter_id++;
|
||||
}
|
||||
}
|
||||
comp_counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cytoplasme) packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients });
|
||||
const { options } = cell;
|
||||
let resultsAsset: Asset.Wrapper<'binary'> | undefined;
|
||||
if (params.resultsFile) {
|
||||
resultsAsset = await plugin.runTask(plugin.managers.asset.resolve(params.resultsFile, 'binary', true));
|
||||
} else if (options?.resultfile) {
|
||||
const url = `${params.baseUrl}/results/${options.resultfile}`;
|
||||
resultsAsset = await plugin.runTask(plugin.managers.asset.resolve(Asset.getUrlAsset(plugin.managers.asset, url), 'binary', true));
|
||||
}
|
||||
if (resultsAsset) {
|
||||
(cache as any).asset = resultsAsset;
|
||||
const results = resultsAsset.data;
|
||||
// flip the byte order if needed
|
||||
const buffer = IsNativeEndianLittle ? results.buffer : flipByteOrder(results, 4);
|
||||
const numbers = new DataView(buffer);
|
||||
const ninst = getFloatValue(numbers, 0);
|
||||
const npoints = getFloatValue(numbers, 4);
|
||||
const ncurve = getFloatValue(numbers, 8);
|
||||
|
||||
let offset = 12;
|
||||
|
||||
if (ninst !== 0) {
|
||||
const pos = new Float32Array(buffer, offset, ninst * 4);
|
||||
offset += ninst * 4 * 4;
|
||||
const quat = new Float32Array(buffer, offset, ninst * 4);
|
||||
offset += ninst * 4 * 4;
|
||||
|
||||
for (let i = 0; i < ninst; i++) {
|
||||
const x: number = pos[i * 4 + 0];
|
||||
const y: number = pos[i * 4 + 1];
|
||||
const z: number = pos[i * 4 + 2];
|
||||
const ingr_id = pos[i * 4 + 3] as number;
|
||||
const pid = cell.mapping_ids![ingr_id];
|
||||
if (!packings[pid[0]].ingredients[pid[1]].results) {
|
||||
packings[pid[0]].ingredients[pid[1]].results = [];
|
||||
}
|
||||
packings[pid[0]].ingredients[pid[1]].results.push([Vec3.create(x, y, z),
|
||||
Quat.create(quat[i * 4 + 0], quat[i * 4 + 1], quat[i * 4 + 2], quat[i * 4 + 3])]);
|
||||
}
|
||||
}
|
||||
|
||||
if (npoints !== 0) {
|
||||
const ctr_pos = new Float32Array(buffer, offset, npoints * 4);
|
||||
offset += npoints * 4 * 4;
|
||||
offset += npoints * 4 * 4;
|
||||
const ctr_info = new Float32Array(buffer, offset, npoints * 4);
|
||||
offset += npoints * 4 * 4;
|
||||
const curve_ids = new Float32Array(buffer, offset, ncurve * 4);
|
||||
offset += ncurve * 4 * 4;
|
||||
|
||||
let counter = 0;
|
||||
let ctr_points: Vec3[] = [];
|
||||
let prev_ctype = 0;
|
||||
let prev_cid = 0;
|
||||
|
||||
for (let i = 0; i < npoints; i++) {
|
||||
const x: number = -ctr_pos[i * 4 + 0];
|
||||
const y: number = ctr_pos[i * 4 + 1];
|
||||
const z: number = ctr_pos[i * 4 + 2];
|
||||
const cid: number = ctr_info[i * 4 + 0]; // curve id
|
||||
const ctype: number = curve_ids[cid * 4 + 0]; // curve type
|
||||
// cid 148 165 -1 0
|
||||
// console.log("cid ",cid,ctype,prev_cid,prev_ctype);//165,148
|
||||
if (prev_ctype !== ctype) {
|
||||
const pid = cell.mapping_ids![-prev_ctype - 1];
|
||||
const cname = `curve${counter}`;
|
||||
packings[pid[0]].ingredients[pid[1]].nbCurve = counter + 1;
|
||||
packings[pid[0]].ingredients[pid[1]][cname] = ctr_points;
|
||||
ctr_points = [];
|
||||
counter = 0;
|
||||
} else if (prev_cid !== cid) {
|
||||
ctr_points = [];
|
||||
const pid = cell.mapping_ids![-prev_ctype - 1];
|
||||
const cname = `curve${counter}`;
|
||||
packings[pid[0]].ingredients[pid[1]][cname] = ctr_points;
|
||||
counter += 1;
|
||||
}
|
||||
ctr_points.push(Vec3.create(x, y, z));
|
||||
prev_ctype = ctype;
|
||||
prev_cid = cid;
|
||||
}
|
||||
|
||||
// do the last one
|
||||
const pid = cell.mapping_ids![-prev_ctype - 1];
|
||||
const cname = `curve${counter}`;
|
||||
packings[pid[0]].ingredients[pid[1]].nbCurve = counter + 1;
|
||||
packings[pid[0]].ingredients[pid[1]][cname] = ctr_points;
|
||||
}
|
||||
}
|
||||
return new CellPack({ cell, packings });
|
||||
});
|
||||
}
|
||||
},
|
||||
dispose({ cache }) {
|
||||
((cache as any)?.asset as Asset.Wrapper | undefined)?.dispose();
|
||||
},
|
||||
});
|
||||
|
||||
export { StructureFromCellpack };
|
||||
@@ -77,9 +229,8 @@ const StructureFromCellpack = PluginStateTransform.BuiltIn({
|
||||
await CellPackInfoProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, structure, {
|
||||
info: { packingsCount: a.data.packings.length, packingIndex: params.packing, colors }
|
||||
});
|
||||
|
||||
(cache as any).assets = assets;
|
||||
return new PSO.Molecule.Structure(structure, { label: packing.name });
|
||||
return new PSO.Molecule.Structure(structure, { label: packing.name + '.' + packing.location });
|
||||
});
|
||||
},
|
||||
dispose({ b, cache }) {
|
||||
@@ -125,7 +276,7 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({
|
||||
const s = await StructureSymmetry.buildAssembly(initial_structure, a.id).runInContext(ctx);
|
||||
structures.push(s);
|
||||
}
|
||||
const builder = Structure.Builder();
|
||||
const builder = Structure.Builder({ label: 'Membrane' });
|
||||
let offsetInvariantId = 0;
|
||||
for (const s of structures) {
|
||||
let maxInvariantId = 0;
|
||||
@@ -148,3 +299,28 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({
|
||||
b?.data.customPropertyDescriptors.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
const CreateTransformer = StateTransformer.builderFactory('cellPACK');
|
||||
export const CreateCompartmentSphere = CreateTransformer({
|
||||
name: 'create-compartment-sphere',
|
||||
display: 'CompartmentSphere',
|
||||
from: PSO.Root, // or whatever data source
|
||||
to: PSO.Shape.Representation3D,
|
||||
params: {
|
||||
center: PD.Vec3(Vec3()),
|
||||
radius: PD.Numeric(1),
|
||||
label: PD.Text(`Compartment Sphere`)
|
||||
}
|
||||
})({
|
||||
canAutoUpdate({ oldParams, newParams }) {
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Compartment Sphere', async ctx => {
|
||||
const data = params;
|
||||
const repr = MBRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => (MBParams));
|
||||
await repr.createOrUpdate({ ...params, quality: 'custom', xrayShaded: true, doubleSided: true }, data).runInContext(ctx);
|
||||
return new PSO.Shape.Representation3D({ repr, sourceData: a }, { label: data.label });
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Ludovic Autin <ludovic.autin@gmail.com>
|
||||
*/
|
||||
|
||||
import { CIF } from '../../mol-io/reader/cif';
|
||||
@@ -37,7 +38,7 @@ async function downloadPDB(plugin: PluginContext, url: string, id: string, asset
|
||||
}
|
||||
|
||||
export async function getFromPdb(plugin: PluginContext, pdbId: string, assetManager: AssetManager) {
|
||||
const { cif, asset } = await downloadCif(plugin, `https://models.rcsb.org/${pdbId.toUpperCase()}.bcif`, true, assetManager);
|
||||
const { cif, asset } = await downloadCif(plugin, `https://models.rcsb.org/${pdbId}.bcif`, true, assetManager);
|
||||
return { mmcif: cif.blocks[0], asset };
|
||||
}
|
||||
|
||||
@@ -74,4 +75,35 @@ export function getStructureMean(structure: Structure) {
|
||||
}
|
||||
const { elementCount } = structure;
|
||||
return Vec3.create(xSum / elementCount, ySum / elementCount, zSum / elementCount);
|
||||
}
|
||||
|
||||
export function getFloatValue(value: DataView, offset: number) {
|
||||
// if the last byte is a negative value (MSB is 1), the final
|
||||
// float should be too
|
||||
const negative = value.getInt8(offset + 2) >>> 31;
|
||||
|
||||
// this is how the bytes are arranged in the byte array/DataView
|
||||
// buffer
|
||||
const [b0, b1, b2, exponent] = [
|
||||
// get first three bytes as unsigned since we only care
|
||||
// about the last 8 bits of 32-bit js number returned by
|
||||
// getUint8().
|
||||
// Should be the same as: getInt8(offset) & -1 >>> 24
|
||||
value.getUint8(offset),
|
||||
value.getUint8(offset + 1),
|
||||
value.getUint8(offset + 2),
|
||||
|
||||
// get the last byte, which is the exponent, as a signed int
|
||||
// since it's already correct
|
||||
value.getInt8(offset + 3)
|
||||
];
|
||||
|
||||
let mantissa = b0 | (b1 << 8) | (b2 << 16);
|
||||
if (negative) {
|
||||
// need to set the most significant 8 bits to 1's since a js
|
||||
// number is 32 bits but our mantissa is only 24.
|
||||
mantissa |= 255 << 24;
|
||||
}
|
||||
|
||||
return mantissa * Math.pow(10, exponent);
|
||||
}
|
||||
36
src/mol-gl/_spec/cylinders.spec.ts
Normal file
36
src/mol-gl/_spec/cylinders.spec.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createRenderObject } from '../render-object';
|
||||
import { Scene } from '../scene';
|
||||
import { getGLContext, tryGetGLContext } from './gl';
|
||||
import { setDebugMode } from '../../mol-util/debug';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Cylinders } from '../../mol-geo/geometry/cylinders/cylinders';
|
||||
|
||||
export function createCylinders() {
|
||||
const cylinders = Cylinders.createEmpty();
|
||||
const props = PD.getDefaultValues(Cylinders.Params);
|
||||
const values = Cylinders.Utils.createValuesSimple(cylinders, props, ColorNames.orange, 1);
|
||||
const state = Cylinders.Utils.createRenderableState(props);
|
||||
return createRenderObject('cylinders', values, state, -1);
|
||||
}
|
||||
|
||||
describe('cylinders', () => {
|
||||
const ctx = tryGetGLContext(32, 32, { fragDepth: true });
|
||||
|
||||
(ctx ? it : it.skip)('basic', async () => {
|
||||
const ctx = getGLContext(32, 32);
|
||||
const scene = Scene.create(ctx);
|
||||
const cylinders = createCylinders();
|
||||
scene.add(cylinders);
|
||||
setDebugMode(true);
|
||||
expect(() => scene.commit()).not.toThrow();
|
||||
setDebugMode(false);
|
||||
ctx.destroy();
|
||||
});
|
||||
});
|
||||
36
src/mol-gl/_spec/direct-volume.spec.ts
Normal file
36
src/mol-gl/_spec/direct-volume.spec.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createRenderObject } from '../render-object';
|
||||
import { Scene } from '../scene';
|
||||
import { getGLContext, tryGetGLContext } from './gl';
|
||||
import { setDebugMode } from '../../mol-util/debug';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { DirectVolume } from '../../mol-geo/geometry/direct-volume/direct-volume';
|
||||
|
||||
export function createDirectVolume() {
|
||||
const directVolume = DirectVolume.createEmpty();
|
||||
const props = PD.getDefaultValues(DirectVolume.Params);
|
||||
const values = DirectVolume.Utils.createValuesSimple(directVolume, props, ColorNames.orange, 1);
|
||||
const state = DirectVolume.Utils.createRenderableState(props);
|
||||
return createRenderObject('direct-volume', values, state, -1);
|
||||
}
|
||||
|
||||
describe('direct-volume', () => {
|
||||
const ctx = tryGetGLContext(32, 32);
|
||||
|
||||
(ctx ? it : it.skip)('basic', async () => {
|
||||
const ctx = getGLContext(32, 32);
|
||||
const scene = Scene.create(ctx);
|
||||
const directVolume = createDirectVolume();
|
||||
scene.add(directVolume);
|
||||
setDebugMode(true);
|
||||
expect(() => scene.commit()).not.toThrow();
|
||||
setDebugMode(false);
|
||||
ctx.destroy();
|
||||
});
|
||||
});
|
||||
@@ -760,8 +760,8 @@ export function createGl(width: number, height: number, contextAttributes: WebGL
|
||||
validateProgram: function () { },
|
||||
generateMipmap: function () { },
|
||||
isContextLost: function () { return false; },
|
||||
drawingBufferWidth: 1024,
|
||||
drawingBufferHeight: 1024,
|
||||
drawingBufferWidth: width,
|
||||
drawingBufferHeight: height,
|
||||
blendColor: function () { },
|
||||
blendEquation: function () { },
|
||||
blendEquationSeparate: function () { },
|
||||
|
||||
28
src/mol-gl/_spec/gl.ts
Normal file
28
src/mol-gl/_spec/gl.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createContext } from '../webgl/context';
|
||||
|
||||
export function getGLContext(width: number, height: number) {
|
||||
const gl = require('gl')(width, height, {
|
||||
alpha: true,
|
||||
depth: true,
|
||||
premultipliedAlpha: true,
|
||||
preserveDrawingBuffer: true,
|
||||
antialias: true,
|
||||
});
|
||||
return createContext(gl);
|
||||
}
|
||||
|
||||
export function tryGetGLContext(width: number, height: number, requiredExtensions?: { fragDepth?: boolean }) {
|
||||
try {
|
||||
const ctx = getGLContext(width, height);
|
||||
if (requiredExtensions?.fragDepth && !ctx.extensions.fragDepth) return;
|
||||
return ctx;
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
36
src/mol-gl/_spec/image.spec.ts
Normal file
36
src/mol-gl/_spec/image.spec.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createRenderObject } from '../render-object';
|
||||
import { Scene } from '../scene';
|
||||
import { getGLContext, tryGetGLContext } from './gl';
|
||||
import { setDebugMode } from '../../mol-util/debug';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Image } from '../../mol-geo/geometry/image/image';
|
||||
|
||||
export function createImage() {
|
||||
const image = Image.createEmpty();
|
||||
const props = PD.getDefaultValues(Image.Params);
|
||||
const values = Image.Utils.createValuesSimple(image, props, ColorNames.orange, 1);
|
||||
const state = Image.Utils.createRenderableState(props);
|
||||
return createRenderObject('image', values, state, -1);
|
||||
}
|
||||
|
||||
describe('image', () => {
|
||||
const ctx = tryGetGLContext(32, 32);
|
||||
|
||||
(ctx ? it : it.skip)('basic', async () => {
|
||||
const ctx = getGLContext(32, 32);
|
||||
const scene = Scene.create(ctx);
|
||||
const image = createImage();
|
||||
scene.add(image);
|
||||
setDebugMode(true);
|
||||
expect(() => scene.commit()).not.toThrow();
|
||||
setDebugMode(false);
|
||||
ctx.destroy();
|
||||
});
|
||||
});
|
||||
36
src/mol-gl/_spec/lines.spec.ts
Normal file
36
src/mol-gl/_spec/lines.spec.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createRenderObject } from '../render-object';
|
||||
import { Scene } from '../scene';
|
||||
import { getGLContext, tryGetGLContext } from './gl';
|
||||
import { setDebugMode } from '../../mol-util/debug';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Lines } from '../../mol-geo/geometry/lines/lines';
|
||||
|
||||
export function createLines() {
|
||||
const lines = Lines.createEmpty();
|
||||
const props = PD.getDefaultValues(Lines.Params);
|
||||
const values = Lines.Utils.createValuesSimple(lines, props, ColorNames.orange, 1);
|
||||
const state = Lines.Utils.createRenderableState(props);
|
||||
return createRenderObject('lines', values, state, -1);
|
||||
}
|
||||
|
||||
describe('lines', () => {
|
||||
const ctx = tryGetGLContext(32, 32);
|
||||
|
||||
(ctx ? it : it.skip)('basic', async () => {
|
||||
const ctx = getGLContext(32, 32);
|
||||
const scene = Scene.create(ctx);
|
||||
const lines = createLines();
|
||||
scene.add(lines);
|
||||
setDebugMode(true);
|
||||
expect(() => scene.commit()).not.toThrow();
|
||||
setDebugMode(false);
|
||||
ctx.destroy();
|
||||
});
|
||||
});
|
||||
36
src/mol-gl/_spec/mesh.spec.ts
Normal file
36
src/mol-gl/_spec/mesh.spec.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createRenderObject } from '../render-object';
|
||||
import { Scene } from '../scene';
|
||||
import { getGLContext, tryGetGLContext } from './gl';
|
||||
import { setDebugMode } from '../../mol-util/debug';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
||||
|
||||
export function createMesh() {
|
||||
const mesh = Mesh.createEmpty();
|
||||
const props = PD.getDefaultValues(Mesh.Params);
|
||||
const values = Mesh.Utils.createValuesSimple(mesh, props, ColorNames.orange, 1);
|
||||
const state = Mesh.Utils.createRenderableState(props);
|
||||
return createRenderObject('mesh', values, state, -1);
|
||||
}
|
||||
|
||||
describe('mesh', () => {
|
||||
const ctx = tryGetGLContext(32, 32);
|
||||
|
||||
(ctx ? it : it.skip)('basic', async () => {
|
||||
const ctx = getGLContext(32, 32);
|
||||
const scene = Scene.create(ctx);
|
||||
const mesh = createMesh();
|
||||
scene.add(mesh);
|
||||
setDebugMode(true);
|
||||
expect(() => scene.commit()).not.toThrow();
|
||||
setDebugMode(false);
|
||||
ctx.destroy();
|
||||
});
|
||||
});
|
||||
36
src/mol-gl/_spec/points.spec.ts
Normal file
36
src/mol-gl/_spec/points.spec.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createRenderObject } from '../render-object';
|
||||
import { Scene } from '../scene';
|
||||
import { getGLContext, tryGetGLContext } from './gl';
|
||||
import { setDebugMode } from '../../mol-util/debug';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Points } from '../../mol-geo/geometry/points/points';
|
||||
|
||||
export function createPoints() {
|
||||
const points = Points.createEmpty();
|
||||
const props = PD.getDefaultValues(Points.Params);
|
||||
const values = Points.Utils.createValuesSimple(points, props, ColorNames.orange, 1);
|
||||
const state = Points.Utils.createRenderableState(props);
|
||||
return createRenderObject('points', values, state, -1);
|
||||
}
|
||||
|
||||
describe('points', () => {
|
||||
const ctx = tryGetGLContext(32, 32);
|
||||
|
||||
(ctx ? it : it.skip)('basic', async () => {
|
||||
const ctx = getGLContext(32, 32);
|
||||
const scene = Scene.create(ctx);
|
||||
const points = createPoints();
|
||||
scene.add(points);
|
||||
setDebugMode(true);
|
||||
expect(() => scene.commit()).not.toThrow();
|
||||
setDebugMode(false);
|
||||
ctx.destroy();
|
||||
});
|
||||
});
|
||||
@@ -5,28 +5,14 @@
|
||||
*/
|
||||
|
||||
import { createGl } from './gl.shim';
|
||||
|
||||
import { Camera } from '../../mol-canvas3d/camera';
|
||||
import { Vec3, Mat4, Vec4 } from '../../mol-math/linear-algebra';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { Renderer } from '../renderer';
|
||||
import { createValueColor } from '../../mol-geo/geometry/color-data';
|
||||
import { createValueSize } from '../../mol-geo/geometry/size-data';
|
||||
import { createContext } from '../webgl/context';
|
||||
import { RenderableState } from '../renderable';
|
||||
import { createRenderObject } from '../render-object';
|
||||
import { PointsValues } from '../renderable/points';
|
||||
import { Scene } from '../scene';
|
||||
import { createEmptyMarkers } from '../../mol-geo/geometry/marker-data';
|
||||
import { fillSerial } from '../../mol-util/array';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { Sphere3D } from '../../mol-math/geometry';
|
||||
import { createEmptyOverpaint } from '../../mol-geo/geometry/overpaint-data';
|
||||
import { createEmptyTransparency } from '../../mol-geo/geometry/transparency-data';
|
||||
import { createEmptyClipping } from '../../mol-geo/geometry/clipping-data';
|
||||
import { createPoints } from './points.spec';
|
||||
|
||||
function createRenderer(gl: WebGLRenderingContext) {
|
||||
export function createRenderer(gl: WebGLRenderingContext) {
|
||||
const ctx = createContext(gl);
|
||||
const camera = new Camera({
|
||||
position: Vec3.create(0, 0, 50)
|
||||
@@ -35,80 +21,14 @@ function createRenderer(gl: WebGLRenderingContext) {
|
||||
return { ctx, camera, renderer };
|
||||
}
|
||||
|
||||
function createPoints() {
|
||||
const aPosition = ValueCell.create(new Float32Array([0, -1, 0, -1, 0, 0, 1, 1, 0]));
|
||||
const aGroup = ValueCell.create(fillSerial(new Float32Array(3)));
|
||||
const aInstance = ValueCell.create(fillSerial(new Float32Array(1)));
|
||||
const color = createValueColor(Color(0xFF0000));
|
||||
const size = createValueSize(1);
|
||||
const marker = createEmptyMarkers();
|
||||
const overpaint = createEmptyOverpaint();
|
||||
const transparency = createEmptyTransparency();
|
||||
const clipping = createEmptyClipping();
|
||||
|
||||
const aTransform = ValueCell.create(new Float32Array(16));
|
||||
const m4 = Mat4.identity();
|
||||
Mat4.toArray(m4, aTransform.ref.value, 0);
|
||||
const transform = ValueCell.create(new Float32Array(aTransform.ref.value));
|
||||
const extraTransform = ValueCell.create(new Float32Array(aTransform.ref.value));
|
||||
|
||||
const boundingSphere = ValueCell.create(Sphere3D.create(Vec3.zero(), 2));
|
||||
const invariantBoundingSphere = ValueCell.create(Sphere3D.create(Vec3.zero(), 2));
|
||||
|
||||
const values: PointsValues = {
|
||||
aPosition,
|
||||
aGroup,
|
||||
aTransform,
|
||||
aInstance,
|
||||
...color,
|
||||
...marker,
|
||||
...size,
|
||||
...overpaint,
|
||||
...transparency,
|
||||
...clipping,
|
||||
|
||||
uAlpha: ValueCell.create(1.0),
|
||||
uVertexCount: ValueCell.create(3),
|
||||
uInstanceCount: ValueCell.create(1),
|
||||
uGroupCount: ValueCell.create(3),
|
||||
uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere.ref.value)),
|
||||
|
||||
alpha: ValueCell.create(1.0),
|
||||
drawCount: ValueCell.create(3),
|
||||
instanceCount: ValueCell.create(1),
|
||||
matrix: ValueCell.create(m4),
|
||||
transform,
|
||||
extraTransform,
|
||||
hasReflection: ValueCell.create(false),
|
||||
boundingSphere,
|
||||
invariantBoundingSphere,
|
||||
|
||||
uSizeFactor: ValueCell.create(1),
|
||||
dPointSizeAttenuation: ValueCell.create(true),
|
||||
dPointStyle: ValueCell.create('square'),
|
||||
};
|
||||
const state: RenderableState = {
|
||||
disposed: false,
|
||||
visible: true,
|
||||
alphaFactor: 1,
|
||||
pickable: true,
|
||||
colorOnly: false,
|
||||
opaque: true,
|
||||
writeDepth: true,
|
||||
noClip: false,
|
||||
};
|
||||
|
||||
return createRenderObject('points', values, state, -1);
|
||||
}
|
||||
|
||||
describe('renderer', () => {
|
||||
it('basic', () => {
|
||||
const [width, height] = [32, 32];
|
||||
const gl = createGl(width, height, { preserveDrawingBuffer: true });
|
||||
const { ctx, renderer } = createRenderer(gl);
|
||||
|
||||
expect(ctx.gl.canvas.width).toBe(32);
|
||||
expect(ctx.gl.canvas.height).toBe(32);
|
||||
expect(ctx.gl.drawingBufferWidth).toBe(32);
|
||||
expect(ctx.gl.drawingBufferHeight).toBe(32);
|
||||
|
||||
expect(ctx.stats.resourceCounts.attribute).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(0);
|
||||
|
||||
36
src/mol-gl/_spec/spheres.spec.ts
Normal file
36
src/mol-gl/_spec/spheres.spec.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createRenderObject } from '../render-object';
|
||||
import { Scene } from '../scene';
|
||||
import { getGLContext, tryGetGLContext } from './gl';
|
||||
import { setDebugMode } from '../../mol-util/debug';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Spheres } from '../../mol-geo/geometry/spheres/spheres';
|
||||
|
||||
export function createSpheres() {
|
||||
const spheres = Spheres.createEmpty();
|
||||
const props = PD.getDefaultValues(Spheres.Params);
|
||||
const values = Spheres.Utils.createValuesSimple(spheres, props, ColorNames.orange, 1);
|
||||
const state = Spheres.Utils.createRenderableState(props);
|
||||
return createRenderObject('spheres', values, state, -1);
|
||||
}
|
||||
|
||||
describe('spheres', () => {
|
||||
const ctx = tryGetGLContext(32, 32, { fragDepth: true });
|
||||
|
||||
(ctx ? it : it.skip)('basic', async () => {
|
||||
const ctx = getGLContext(32, 32);
|
||||
const scene = Scene.create(ctx);
|
||||
const spheres = createSpheres();
|
||||
scene.add(spheres);
|
||||
setDebugMode(true);
|
||||
expect(() => scene.commit()).not.toThrow();
|
||||
setDebugMode(false);
|
||||
ctx.destroy();
|
||||
});
|
||||
});
|
||||
36
src/mol-gl/_spec/text.spec.ts
Normal file
36
src/mol-gl/_spec/text.spec.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createRenderObject } from '../render-object';
|
||||
import { Scene } from '../scene';
|
||||
import { getGLContext, tryGetGLContext } from './gl';
|
||||
import { setDebugMode } from '../../mol-util/debug';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Text } from '../../mol-geo/geometry/text/text';
|
||||
|
||||
export function createText() {
|
||||
const text = Text.createEmpty();
|
||||
const props = PD.getDefaultValues(Text.Params);
|
||||
const values = Text.Utils.createValuesSimple(text, props, ColorNames.orange, 1);
|
||||
const state = Text.Utils.createRenderableState(props);
|
||||
return createRenderObject('text', values, state, -1);
|
||||
}
|
||||
|
||||
describe('text', () => {
|
||||
const ctx = tryGetGLContext(32, 32);
|
||||
|
||||
(ctx ? it : it.skip)('basic', async () => {
|
||||
const ctx = getGLContext(32, 32);
|
||||
const scene = Scene.create(ctx);
|
||||
const text = createText();
|
||||
scene.add(text);
|
||||
setDebugMode(true);
|
||||
expect(() => scene.commit()).not.toThrow();
|
||||
setDebugMode(false);
|
||||
ctx.destroy();
|
||||
});
|
||||
});
|
||||
36
src/mol-gl/_spec/texture-mesh.spec.ts
Normal file
36
src/mol-gl/_spec/texture-mesh.spec.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createRenderObject } from '../render-object';
|
||||
import { Scene } from '../scene';
|
||||
import { getGLContext, tryGetGLContext } from './gl';
|
||||
import { setDebugMode } from '../../mol-util/debug';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { TextureMesh } from '../../mol-geo/geometry/texture-mesh/texture-mesh';
|
||||
|
||||
export function createTextureMesh() {
|
||||
const textureMesh = TextureMesh.createEmpty();
|
||||
const props = PD.getDefaultValues(TextureMesh.Params);
|
||||
const values = TextureMesh.Utils.createValuesSimple(textureMesh, props, ColorNames.orange, 1);
|
||||
const state = TextureMesh.Utils.createRenderableState(props);
|
||||
return createRenderObject('texture-mesh', values, state, -1);
|
||||
}
|
||||
|
||||
describe('texture-mesh', () => {
|
||||
const ctx = tryGetGLContext(32, 32);
|
||||
|
||||
(ctx ? it : it.skip)('basic', async () => {
|
||||
const ctx = getGLContext(32, 32);
|
||||
const scene = Scene.create(ctx);
|
||||
const textureMesh = createTextureMesh();
|
||||
scene.add(textureMesh);
|
||||
setDebugMode(true);
|
||||
expect(() => scene.commit()).not.toThrow();
|
||||
setDebugMode(false);
|
||||
ctx.destroy();
|
||||
});
|
||||
});
|
||||
@@ -3,9 +3,8 @@ export const assign_material_color = `
|
||||
#if defined(dMarkerType_uniform)
|
||||
float marker = uMarker;
|
||||
#elif defined(dMarkerType_groupInstance)
|
||||
float marker = vMarker;
|
||||
float marker = floor(vMarker * 255.0 + 0.5); // rounding required to work on some cards on win
|
||||
#endif
|
||||
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
|
||||
#endif
|
||||
|
||||
#if defined(dRenderVariant_color)
|
||||
|
||||
@@ -358,7 +358,10 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal
|
||||
unbindResources(gl);
|
||||
|
||||
// to aid GC
|
||||
if (!options?.doNotForceWebGLContextLoss) gl.getExtension('WEBGL_lose_context')?.loseContext();
|
||||
if (!options?.doNotForceWebGLContextLoss) {
|
||||
gl.getExtension('WEBGL_lose_context')?.loseContext();
|
||||
gl.getExtension('STACKGL_destroy_context')?.destroy();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -48,7 +48,7 @@ namespace Axes3D {
|
||||
return out;
|
||||
}
|
||||
|
||||
const tmpTransformMat3 = Mat3.zero();
|
||||
const tmpTransformMat3 = Mat3();
|
||||
/** Transform axes with a Mat4 */
|
||||
export function transform(out: Axes3D, a: Axes3D, m: Mat4): Axes3D {
|
||||
Vec3.transformMat4(out.origin, a.origin, m);
|
||||
@@ -58,6 +58,13 @@ namespace Axes3D {
|
||||
Vec3.transformMat3(out.dirC, a.dirC, n);
|
||||
return out;
|
||||
}
|
||||
|
||||
export function scale(out: Axes3D, a: Axes3D, scale: number): Axes3D {
|
||||
Vec3.scale(out.dirA, a.dirA, scale);
|
||||
Vec3.scale(out.dirB, a.dirB, scale);
|
||||
Vec3.scale(out.dirC, a.dirC, scale);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
export { Axes3D };
|
||||
@@ -25,7 +25,7 @@ export function parseCmpnd(lines: Tokens, lineStart: number, lineEnd: number) {
|
||||
|
||||
let currentSpec: Spec | undefined;
|
||||
let currentCompound: EntityCompound = { chains: [], description: '' };
|
||||
const Compounds: EntityCompound[] = [];
|
||||
const compounds: EntityCompound[] = [];
|
||||
|
||||
for (let i = lineStart; i < lineEnd; i++) {
|
||||
const line = getLine(i);
|
||||
@@ -55,7 +55,7 @@ export function parseCmpnd(lines: Tokens, lineStart: number, lineEnd: number) {
|
||||
chains: [],
|
||||
description: ''
|
||||
};
|
||||
Compounds.push(currentCompound);
|
||||
compounds.push(currentCompound);
|
||||
} else if (currentSpec === 'MOLECULE') {
|
||||
if (currentCompound.description) currentCompound.description += ' ';
|
||||
currentCompound.description += value;
|
||||
@@ -64,7 +64,31 @@ export function parseCmpnd(lines: Tokens, lineStart: number, lineEnd: number) {
|
||||
}
|
||||
}
|
||||
|
||||
return Compounds;
|
||||
// Define a seprate entity for each chain
|
||||
// --------------------------------------
|
||||
//
|
||||
// This is a workaround for how sequences are currently determined for PDB files.
|
||||
//
|
||||
// The current approach infers the "observed sequence" from the atomic hierarchy.
|
||||
// However, for example for PDB ID 3HHR, this approach fails, since chains B and C
|
||||
// belong to the same entity but contain different observed sequence, which causes display
|
||||
// errors in the sequence viewer (since the sequences are determined "per entity").
|
||||
//
|
||||
// A better approach could be to parse SEQRES categories and use it to construct
|
||||
// entity_poly_seq category. However, this would require constructing label_seq_id (with gaps)
|
||||
// from RES ID pdb column (auth_seq_id), which isn't a trivial exercise.
|
||||
//
|
||||
// (properly formatted) mmCIF structures do not exhibit this issue.
|
||||
const singletons: EntityCompound[] = [];
|
||||
for (const comp of compounds) {
|
||||
for (const chain of comp.chains) {
|
||||
singletons.push({
|
||||
description: comp.description,
|
||||
chains: [chain]
|
||||
});
|
||||
}
|
||||
}
|
||||
return singletons;
|
||||
}
|
||||
|
||||
export function parseHetnam(lines: Tokens, lineStart: number, lineEnd: number) {
|
||||
|
||||
@@ -582,6 +582,20 @@ export namespace Loci {
|
||||
return PrincipalAxes.ofPositions(positions);
|
||||
}
|
||||
|
||||
export function getPrincipalAxesMany(locis: Loci[]): PrincipalAxes {
|
||||
let elementCount = 0;
|
||||
locis.forEach(l => {
|
||||
elementCount += size(l);
|
||||
});
|
||||
const positions = new Float32Array(3 * elementCount);
|
||||
let offset = 0;
|
||||
locis.forEach(l => {
|
||||
toPositionsArray(l, positions, offset);
|
||||
offset += size(l) * 3;
|
||||
});
|
||||
return PrincipalAxes.ofPositions(positions);
|
||||
}
|
||||
|
||||
function sourceIndex(unit: Unit, element: ElementIndex) {
|
||||
return Unit.isAtomic(unit)
|
||||
? unit.model.atomicHierarchy.atomSourceIndex.value(element)
|
||||
|
||||
@@ -79,7 +79,7 @@ function findIndexPairBonds(unit: Unit.Atomic) {
|
||||
if ((d !== -1 && equalEps(dist, d, 0.5)) || dist < maxDistance) {
|
||||
atomA[atomA.length] = _aI;
|
||||
atomB[atomB.length] = _bI;
|
||||
orders[order.length] = order[i];
|
||||
orders[orders.length] = order[i];
|
||||
flags[flags.length] = flag[i];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,6 +379,8 @@ const atomicDetail = StructureRepresentationPresetProvider({
|
||||
}
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
await updateFocusRepr(plugin, structure, params.theme?.focus?.name ?? color, params.theme?.focus?.params ?? colorParams);
|
||||
|
||||
return { components, representations };
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-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>
|
||||
*/
|
||||
|
||||
import { StructureElement } from '../../../mol-model/structure';
|
||||
@@ -15,10 +16,13 @@ import { StatefulPluginComponent } from '../../component';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { MeasurementRepresentationCommonTextParams, LociLabelTextParams } from '../../../mol-repr/shape/loci/common';
|
||||
import { LineParams } from '../../../mol-repr/structure/representation/line';
|
||||
import { Expression } from '../../../mol-script/language/expression';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
|
||||
export { StructureMeasurementManager };
|
||||
|
||||
export const MeasurementGroupTag = 'measurement-group';
|
||||
export const MeasurementOrderLabelTag = 'measurement-order-label';
|
||||
|
||||
export type StructureMeasurementCell = StateObjectCell<PluginStateObject.Shape.Representation3D, StateTransform<StateTransformer<PluginStateObject.Molecule.Structure.Selections, PluginStateObject.Shape.Representation3D, any>>>
|
||||
|
||||
@@ -35,6 +39,7 @@ export interface StructureMeasurementManagerState {
|
||||
angles: StructureMeasurementCell[],
|
||||
dihedrals: StructureMeasurementCell[],
|
||||
orientations: StructureMeasurementCell[],
|
||||
planes: StructureMeasurementCell[],
|
||||
options: StructureMeasurementOptions
|
||||
}
|
||||
|
||||
@@ -222,19 +227,25 @@ class StructureMeasurementManager extends StatefulPluginComponent<StructureMeasu
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
|
||||
}
|
||||
|
||||
async addOrientation(a: StructureElement.Loci) {
|
||||
const cellA = this.plugin.helpers.substructureParent.get(a.structure);
|
||||
async addOrientation(locis: StructureElement.Loci[]) {
|
||||
const selections: { key: string, ref: string, groupId?: string, expression: Expression }[] = [];
|
||||
const dependsOn: string[] = [];
|
||||
|
||||
if (!cellA) return;
|
||||
for (let i = 0, il = locis.length; i < il; ++i) {
|
||||
const l = locis[i];
|
||||
const cell = this.plugin.helpers.substructureParent.get(l.structure);
|
||||
if (!cell) continue;
|
||||
|
||||
const dependsOn = [cellA.transform.ref];
|
||||
arraySetAdd(dependsOn, cell.transform.ref);
|
||||
selections.push({ key: `l${i}`, ref: cell.transform.ref, expression: StructureElement.Loci.toExpression(l) });
|
||||
}
|
||||
|
||||
if (selections.length === 0) return;
|
||||
|
||||
const update = this.getGroup();
|
||||
update
|
||||
.apply(StateTransforms.Model.MultiStructureSelectionFromExpression, {
|
||||
selections: [
|
||||
{ key: 'a', ref: cellA.transform.ref, expression: StructureElement.Loci.toExpression(a) },
|
||||
],
|
||||
selections,
|
||||
isTransitive: true,
|
||||
label: 'Orientation'
|
||||
}, { dependsOn })
|
||||
@@ -244,6 +255,69 @@ class StructureMeasurementManager extends StatefulPluginComponent<StructureMeasu
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
|
||||
}
|
||||
|
||||
async addPlane(locis: StructureElement.Loci[]) {
|
||||
const selections: { key: string, ref: string, groupId?: string, expression: Expression }[] = [];
|
||||
const dependsOn: string[] = [];
|
||||
|
||||
for (let i = 0, il = locis.length; i < il; ++i) {
|
||||
const l = locis[i];
|
||||
const cell = this.plugin.helpers.substructureParent.get(l.structure);
|
||||
if (!cell) continue;
|
||||
|
||||
arraySetAdd(dependsOn, cell.transform.ref);
|
||||
selections.push({ key: `l${i}`, ref: cell.transform.ref, expression: StructureElement.Loci.toExpression(l) });
|
||||
}
|
||||
|
||||
if (selections.length === 0) return;
|
||||
|
||||
const update = this.getGroup();
|
||||
update
|
||||
.apply(StateTransforms.Model.MultiStructureSelectionFromExpression, {
|
||||
selections,
|
||||
isTransitive: true,
|
||||
label: 'Plane'
|
||||
}, { dependsOn })
|
||||
.apply(StateTransforms.Representation.StructureSelectionsPlane3D);
|
||||
|
||||
const state = this.plugin.state.data;
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
|
||||
}
|
||||
|
||||
async addOrderLabels(locis: StructureElement.Loci[]) {
|
||||
const update = this.getGroup();
|
||||
|
||||
const current = this.plugin.state.data.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Selections).withTag(MeasurementOrderLabelTag));
|
||||
for (const obj of current)
|
||||
update.delete(obj);
|
||||
|
||||
let order = 1;
|
||||
for (const loci of locis) {
|
||||
const cell = this.plugin.helpers.substructureParent.get(loci.structure);
|
||||
if (!cell) continue;
|
||||
|
||||
const dependsOn = [cell.transform.ref];
|
||||
|
||||
update
|
||||
.apply(StateTransforms.Model.MultiStructureSelectionFromExpression, {
|
||||
selections: [
|
||||
{ key: 'a', ref: cell.transform.ref, expression: StructureElement.Loci.toExpression(loci) },
|
||||
],
|
||||
isTransitive: true,
|
||||
label: 'Order'
|
||||
}, { dependsOn, tags: MeasurementOrderLabelTag })
|
||||
.apply(StateTransforms.Representation.StructureSelectionsLabel3D, {
|
||||
textColor: Color.fromRgb(255, 255, 255),
|
||||
borderColor: Color.fromRgb(0, 0, 0),
|
||||
borderWidth: 0.5,
|
||||
textSize: 0.33,
|
||||
customText: `${order++}`
|
||||
}, { tags: MeasurementOrderLabelTag });
|
||||
}
|
||||
|
||||
const state = this.plugin.state.data;
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
|
||||
}
|
||||
|
||||
private _empty: any[] = [];
|
||||
private getTransforms<T extends StateTransformer<A, B, any>, A extends PluginStateObject.Molecule.Structure.Selections, B extends StateObject>(transformer: T) {
|
||||
const state = this.plugin.state.data;
|
||||
@@ -254,18 +328,26 @@ class StructureMeasurementManager extends StatefulPluginComponent<StructureMeasu
|
||||
}
|
||||
|
||||
private sync() {
|
||||
const labels = [];
|
||||
for (const cell of this.getTransforms(StateTransforms.Representation.StructureSelectionsLabel3D) as StructureMeasurementCell[]) {
|
||||
const tags = (cell.obj as any)['tags'] as string[];
|
||||
if (!tags || !tags.includes(MeasurementOrderLabelTag))
|
||||
labels.push(cell);
|
||||
}
|
||||
|
||||
const updated = this.updateState({
|
||||
labels: this.getTransforms(StateTransforms.Representation.StructureSelectionsLabel3D),
|
||||
labels,
|
||||
distances: this.getTransforms(StateTransforms.Representation.StructureSelectionsDistance3D),
|
||||
angles: this.getTransforms(StateTransforms.Representation.StructureSelectionsAngle3D),
|
||||
dihedrals: this.getTransforms(StateTransforms.Representation.StructureSelectionsDihedral3D),
|
||||
orientations: this.getTransforms(StateTransforms.Representation.StructureSelectionsOrientation3D)
|
||||
orientations: this.getTransforms(StateTransforms.Representation.StructureSelectionsOrientation3D),
|
||||
planes: this.getTransforms(StateTransforms.Representation.StructureSelectionsPlane3D),
|
||||
});
|
||||
if (updated) this.stateUpdated();
|
||||
}
|
||||
|
||||
constructor(private plugin: PluginContext) {
|
||||
super({ labels: [], distances: [], angles: [], dihedrals: [], orientations: [], options: DefaultStructureMeasurementOptions });
|
||||
super({ labels: [], distances: [], angles: [], dihedrals: [], orientations: [], planes: [], options: DefaultStructureMeasurementOptions });
|
||||
|
||||
plugin.state.data.events.changed.subscribe(e => {
|
||||
if (e.inTransaction || plugin.behaviors.state.isAnimating.value) return;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-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>
|
||||
@@ -22,6 +22,7 @@ import { PluginStateObject as PSO } from '../../objects';
|
||||
import { UUID } from '../../../mol-util';
|
||||
import { StructureRef } from './hierarchy-state';
|
||||
import { Boundary } from '../../../mol-math/geometry/boundary';
|
||||
import { iterableToArray } from '../../../mol-data/util';
|
||||
|
||||
interface StructureSelectionManagerState {
|
||||
entries: Map<string, SelectionEntry>,
|
||||
@@ -405,14 +406,8 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
|
||||
}
|
||||
|
||||
getPrincipalAxes(): PrincipalAxes {
|
||||
const elementCount = this.elementCount();
|
||||
const positions = new Float32Array(3 * elementCount);
|
||||
let offset = 0;
|
||||
this.entries.forEach(v => {
|
||||
StructureElement.Loci.toPositionsArray(v.selection, positions, offset);
|
||||
offset += StructureElement.Loci.size(v.selection) * 3;
|
||||
});
|
||||
return PrincipalAxes.ofPositions(positions);
|
||||
const values = iterableToArray(this.entries.values());
|
||||
return StructureElement.Loci.getPrincipalAxesMany(values.map(v => v.selection));
|
||||
}
|
||||
|
||||
modify(modifier: StructureSelectionModifier, loci: Loci) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -10,6 +10,7 @@ import { LabelData } from '../../mol-repr/shape/loci/label';
|
||||
import { OrientationData } from '../../mol-repr/shape/loci/orientation';
|
||||
import { AngleData } from '../../mol-repr/shape/loci/angle';
|
||||
import { DihedralData } from '../../mol-repr/shape/loci/dihedral';
|
||||
import { PlaneData } from '../../mol-repr/shape/loci/plane';
|
||||
|
||||
export function getDistanceDataFromStructureSelections(s: ReadonlyArray<PluginStateObject.Molecule.Structure.SelectionEntry>): DistanceData {
|
||||
const lociA = s[0].loci;
|
||||
@@ -38,6 +39,9 @@ export function getLabelDataFromStructureSelections(s: ReadonlyArray<PluginState
|
||||
}
|
||||
|
||||
export function getOrientationDataFromStructureSelections(s: ReadonlyArray<PluginStateObject.Molecule.Structure.SelectionEntry>): OrientationData {
|
||||
const loci = s[0].loci;
|
||||
return { locis: [loci] };
|
||||
return { locis: s.map(v => v.loci) };
|
||||
}
|
||||
|
||||
export function getPlaneDataFromStructureSelections(s: ReadonlyArray<PluginStateObject.Molecule.Structure.SelectionEntry>): PlaneData {
|
||||
return { locis: s.map(v => v.loci) };
|
||||
}
|
||||
@@ -645,7 +645,8 @@ const MultiStructureSelectionFromExpression = PluginStateTransform.BuiltIn({
|
||||
totalSize += StructureElement.Loci.size(loci.loci);
|
||||
|
||||
continue;
|
||||
} if (entry.expression !== sel.expression) {
|
||||
}
|
||||
if (entry.expression !== sel.expression) {
|
||||
recreate = true;
|
||||
} else {
|
||||
// TODO: properly support "transitive" queries. For that Structure.areUnitAndIndicesEqual needs to be fixed;
|
||||
|
||||
@@ -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>
|
||||
@@ -28,7 +28,7 @@ import { BaseGeometry } from '../../mol-geo/geometry/base';
|
||||
import { Script } from '../../mol-script/script';
|
||||
import { UnitcellParams, UnitcellRepresentation, getUnitcellData } from '../../mol-repr/shape/model/unitcell';
|
||||
import { DistanceParams, DistanceRepresentation } from '../../mol-repr/shape/loci/distance';
|
||||
import { getDistanceDataFromStructureSelections, getLabelDataFromStructureSelections, getOrientationDataFromStructureSelections, getAngleDataFromStructureSelections, getDihedralDataFromStructureSelections } from './helpers';
|
||||
import { getDistanceDataFromStructureSelections, getLabelDataFromStructureSelections, getOrientationDataFromStructureSelections, getAngleDataFromStructureSelections, getDihedralDataFromStructureSelections, getPlaneDataFromStructureSelections } from './helpers';
|
||||
import { LabelParams, LabelRepresentation } from '../../mol-repr/shape/loci/label';
|
||||
import { OrientationRepresentation, OrientationParams } from '../../mol-repr/shape/loci/orientation';
|
||||
import { AngleParams, AngleRepresentation } from '../../mol-repr/shape/loci/angle';
|
||||
@@ -40,6 +40,7 @@ import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
||||
import { getBoxMesh } from './shape';
|
||||
import { Shape } from '../../mol-model/shape';
|
||||
import { Box3D } from '../../mol-math/geometry';
|
||||
import { PlaneParams, PlaneRepresentation } from '../../mol-repr/shape/loci/plane';
|
||||
|
||||
export { StructureRepresentation3D };
|
||||
export { ExplodeStructureRepresentation3D };
|
||||
@@ -986,4 +987,37 @@ const StructureSelectionsOrientation3D = PluginStateTransform.BuiltIn({
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export { StructureSelectionsPlane3D };
|
||||
type StructureSelectionsPlane3D = typeof StructureSelectionsPlane3D
|
||||
const StructureSelectionsPlane3D = PluginStateTransform.BuiltIn({
|
||||
name: 'structure-selections-plane-3d',
|
||||
display: '3D Plane',
|
||||
from: SO.Molecule.Structure.Selections,
|
||||
to: SO.Shape.Representation3D,
|
||||
params: () => ({
|
||||
...PlaneParams,
|
||||
})
|
||||
})({
|
||||
canAutoUpdate({ oldParams, newParams }) {
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Structure Plane', async ctx => {
|
||||
const data = getPlaneDataFromStructureSelections(a.data);
|
||||
const repr = PlaneRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => PlaneParams);
|
||||
await repr.createOrUpdate(params, data).runInContext(ctx);
|
||||
return new SO.Shape.Representation3D({ repr, sourceData: data }, { label: `Plane` });
|
||||
});
|
||||
},
|
||||
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
|
||||
return Task.create('Structure Plane', async ctx => {
|
||||
const props = { ...b.data.repr.props, ...newParams };
|
||||
const data = getPlaneDataFromStructureSelections(a.data);
|
||||
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
|
||||
b.data.sourceData = data;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -25,6 +25,9 @@ import { StructureSelectionManager } from '../mol-plugin-state/manager/structure
|
||||
import { arrayEqual } from '../mol-util/array';
|
||||
|
||||
const MaxDisplaySequenceLength = 5000;
|
||||
// TODO: add virtualized Select controls (at best with a search box)?
|
||||
const MaxSelectOptionsCount = 1000;
|
||||
const MaxSequenceWrappersCount = 30;
|
||||
|
||||
function opKey(l: StructureElement.Location) {
|
||||
const ids = SP.unit.pdbx_struct_oper_list_ids(l);
|
||||
@@ -94,7 +97,7 @@ function getSequenceWrapper(state: { structure: Structure, modelEntityId: string
|
||||
}
|
||||
}
|
||||
|
||||
function getModelEntityOptions(structure: Structure, polymersOnly = false) {
|
||||
function getModelEntityOptions(structure: Structure, polymersOnly = false): [string, string][] {
|
||||
const options: [string, string][] = [];
|
||||
const l = StructureElement.Location.create(structure);
|
||||
const seen = new Set<string>();
|
||||
@@ -118,13 +121,17 @@ function getModelEntityOptions(structure: Structure, polymersOnly = false) {
|
||||
const label = `${id}: ${description}`;
|
||||
options.push([key, label]);
|
||||
seen.add(key);
|
||||
|
||||
if (options.length > MaxSelectOptionsCount) {
|
||||
return [['', 'Too many entities']];
|
||||
}
|
||||
}
|
||||
|
||||
if (options.length === 0) options.push(['', 'No entities']);
|
||||
return options;
|
||||
}
|
||||
|
||||
function getChainOptions(structure: Structure, modelEntityId: string) {
|
||||
function getChainOptions(structure: Structure, modelEntityId: string): [number, string][] {
|
||||
const options: [number, string][] = [];
|
||||
const l = StructureElement.Location.create(structure);
|
||||
const seen = new Set<number>();
|
||||
@@ -144,13 +151,17 @@ function getChainOptions(structure: Structure, modelEntityId: string) {
|
||||
|
||||
options.push([id, label]);
|
||||
seen.add(id);
|
||||
|
||||
if (options.length > MaxSelectOptionsCount) {
|
||||
return [[-1, 'Too many chains']];
|
||||
}
|
||||
}
|
||||
|
||||
if (options.length === 0) options.push([-1, 'No units']);
|
||||
if (options.length === 0) options.push([-1, 'No chains']);
|
||||
return options;
|
||||
}
|
||||
|
||||
function getOperatorOptions(structure: Structure, modelEntityId: string, chainGroupId: number) {
|
||||
function getOperatorOptions(structure: Structure, modelEntityId: string, chainGroupId: number): [string, string][] {
|
||||
const options: [string, string][] = [];
|
||||
const l = StructureElement.Location.create(structure);
|
||||
const seen = new Set<string>();
|
||||
@@ -168,6 +179,10 @@ function getOperatorOptions(structure: Structure, modelEntityId: string, chainGr
|
||||
const label = unit.conformation.operator.name;
|
||||
options.push([id, label]);
|
||||
seen.add(id);
|
||||
|
||||
if (options.length > MaxSelectOptionsCount) {
|
||||
return [['', 'Too many operators']];
|
||||
}
|
||||
}
|
||||
|
||||
if (options.length === 0) options.push(['', 'No operators']);
|
||||
@@ -266,6 +281,7 @@ export class SequenceView extends PluginUIComponent<{ defaultMode?: SequenceView
|
||||
}, this.plugin.managers.structure.selection),
|
||||
label: `${cLabel} | ${eLabel}`
|
||||
});
|
||||
if (wrappers.length > MaxSequenceWrappersCount) return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 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>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -15,7 +15,8 @@ import { AngleData } from '../../mol-repr/shape/loci/angle';
|
||||
import { DihedralData } from '../../mol-repr/shape/loci/dihedral';
|
||||
import { DistanceData } from '../../mol-repr/shape/loci/distance';
|
||||
import { LabelData } from '../../mol-repr/shape/loci/label';
|
||||
import { angleLabel, dihedralLabel, distanceLabel, lociLabel } from '../../mol-theme/label';
|
||||
import { OrientationData } from '../../mol-repr/shape/loci/orientation';
|
||||
import { angleLabel, dihedralLabel, distanceLabel, lociLabel, structureElementLociLabelMany } from '../../mol-theme/label';
|
||||
import { FiniteArray } from '../../mol-util/type-helpers';
|
||||
import { CollapsableControls, PurePluginUIComponent } from '../base';
|
||||
import { ActionMenu } from '../controls/action-menu';
|
||||
@@ -61,12 +62,13 @@ export class MeasurementList extends PurePluginUIComponent {
|
||||
|
||||
render() {
|
||||
const measurements = this.plugin.managers.structure.measurement.state;
|
||||
|
||||
return <div style={{ marginTop: '6px' }}>
|
||||
{this.renderGroup(measurements.labels, 'Labels')}
|
||||
{this.renderGroup(measurements.distances, 'Distances')}
|
||||
{this.renderGroup(measurements.angles, 'Angles')}
|
||||
{this.renderGroup(measurements.dihedrals, 'Dihedrals')}
|
||||
{this.renderGroup(measurements.orientations, 'Orientations')}
|
||||
{this.renderGroup(measurements.planes, 'Planes')}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -77,6 +79,7 @@ export class MeasurementControls extends PurePluginUIComponent<{}, { isBusy: boo
|
||||
componentDidMount() {
|
||||
this.subscribe(this.selection.events.additionsHistoryUpdated, () => {
|
||||
this.forceUpdate();
|
||||
this.updateOrderLabels();
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.behaviors.state.isBusy, v => {
|
||||
@@ -84,6 +87,33 @@ export class MeasurementControls extends PurePluginUIComponent<{}, { isBusy: boo
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.clearOrderLabels();
|
||||
super.componentWillUnmount();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: {}, prevState: { isBusy: boolean, action?: 'add' | 'options' }) {
|
||||
if (this.state.action !== prevState.action)
|
||||
this.updateOrderLabels();
|
||||
}
|
||||
|
||||
clearOrderLabels() {
|
||||
this.plugin.managers.structure.measurement.addOrderLabels([]);
|
||||
}
|
||||
|
||||
updateOrderLabels() {
|
||||
if (this.state.action !== 'add') {
|
||||
this.clearOrderLabels();
|
||||
return;
|
||||
}
|
||||
|
||||
const locis = [];
|
||||
const history = this.selection.additionsHistory;
|
||||
for (let idx = 0; idx < history.length && idx < 4; idx++)
|
||||
locis.push(history[idx].loci);
|
||||
this.plugin.managers.structure.measurement.addOrderLabels(locis);
|
||||
}
|
||||
|
||||
get selection() {
|
||||
return this.plugin.managers.structure.selection;
|
||||
}
|
||||
@@ -108,13 +138,31 @@ export class MeasurementControls extends PurePluginUIComponent<{}, { isBusy: boo
|
||||
this.plugin.managers.structure.measurement.addLabel(loci[0].loci);
|
||||
}
|
||||
|
||||
addOrientation = () => {
|
||||
const locis: StructureElement.Loci[] = [];
|
||||
this.plugin.managers.structure.selection.entries.forEach(v => {
|
||||
locis.push(v.selection);
|
||||
});
|
||||
this.plugin.managers.structure.measurement.addOrientation(locis);
|
||||
}
|
||||
|
||||
addPlane = () => {
|
||||
const locis: StructureElement.Loci[] = [];
|
||||
this.plugin.managers.structure.selection.entries.forEach(v => {
|
||||
locis.push(v.selection);
|
||||
});
|
||||
this.plugin.managers.structure.measurement.addPlane(locis);
|
||||
}
|
||||
|
||||
get actions(): ActionMenu.Items {
|
||||
const history = this.selection.additionsHistory;
|
||||
const ret: ActionMenu.Item[] = [
|
||||
{ kind: 'item', label: `Label ${history.length === 0 ? ' (1 selection required)' : ' (1st selection)'}`, value: this.addLabel, disabled: history.length === 0 },
|
||||
{ kind: 'item', label: `Distance ${history.length < 2 ? ' (2 selections required)' : ' (top 2 selections)'}`, value: this.measureDistance, disabled: history.length < 2 },
|
||||
{ kind: 'item', label: `Angle ${history.length < 3 ? ' (3 selections required)' : ' (top 3 selections)'}`, value: this.measureAngle, disabled: history.length < 3 },
|
||||
{ kind: 'item', label: `Dihedral ${history.length < 4 ? ' (4 selections required)' : ' (top 4 selections)'}`, value: this.measureDihedral, disabled: history.length < 4 },
|
||||
{ kind: 'item', label: `Label ${history.length === 0 ? ' (1 selection item required)' : ' (1st selection item)'}`, value: this.addLabel, disabled: history.length === 0 },
|
||||
{ kind: 'item', label: `Distance ${history.length < 2 ? ' (2 selection items required)' : ' (top 2 selection items)'}`, value: this.measureDistance, disabled: history.length < 2 },
|
||||
{ kind: 'item', label: `Angle ${history.length < 3 ? ' (3 selection items required)' : ' (top 3 items)'}`, value: this.measureAngle, disabled: history.length < 3 },
|
||||
{ kind: 'item', label: `Dihedral ${history.length < 4 ? ' (4 selection items required)' : ' (top 4 selection items)'}`, value: this.measureDihedral, disabled: history.length < 4 },
|
||||
{ kind: 'item', label: `Orientation ${history.length === 0 ? ' (selection required)' : ' (current selection)'}`, value: this.addOrientation, disabled: history.length === 0 },
|
||||
{ kind: 'item', label: `Plane ${history.length === 0 ? ' (selection required)' : ' (current selection)'}`, value: this.addPlane, disabled: history.length === 0 },
|
||||
];
|
||||
return ret;
|
||||
}
|
||||
@@ -142,8 +190,8 @@ export class MeasurementControls extends PurePluginUIComponent<{}, { isBusy: boo
|
||||
|
||||
historyEntry(e: StructureSelectionHistoryEntry, idx: number) {
|
||||
const history = this.plugin.managers.structure.selection.additionsHistory;
|
||||
return <div className='msp-flex-row' key={e.id}>
|
||||
<Button noOverflow title='Click to focus. Hover to highlight.' onClick={() => this.focusLoci(e.loci)} style={{ width: 'auto', textAlign: 'left' }} onMouseEnter={() => this.highlight(e.loci)} onMouseLeave={() => this.plugin.managers.interactivity.lociHighlights.clearHighlights()}>
|
||||
return <div className='msp-flex-row' key={e.id} onMouseEnter={() => this.highlight(e.loci)} onMouseLeave={() => this.plugin.managers.interactivity.lociHighlights.clearHighlights()}>
|
||||
<Button noOverflow title='Click to focus. Hover to highlight.' onClick={() => this.focusLoci(e.loci)} style={{ width: 'auto', textAlign: 'left' }}>
|
||||
{idx}. <span dangerouslySetInnerHTML={{ __html: e.label }} />
|
||||
</Button>
|
||||
{history.length > 1 && <IconButton svg={ArrowUpwardSvg} small={true} className='msp-form-control' onClick={() => this.moveHistory(e, 'up')} flex='20px' title={'Move up'} />}
|
||||
@@ -219,7 +267,7 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
|
||||
}
|
||||
|
||||
get selections() {
|
||||
return this.props.cell.obj?.data.sourceData as Partial<DistanceData & AngleData & DihedralData & LabelData> | undefined;
|
||||
return this.props.cell.obj?.data.sourceData as Partial<DistanceData & AngleData & DihedralData & LabelData & OrientationData> | undefined;
|
||||
}
|
||||
|
||||
delete = () => {
|
||||
@@ -266,6 +314,7 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
|
||||
if (selections.pairs) return selections.pairs[0].loci;
|
||||
if (selections.triples) return selections.triples[0].loci;
|
||||
if (selections.quads) return selections.quads[0].loci;
|
||||
if (selections.locis) return selections.locis;
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -277,6 +326,7 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
|
||||
if (selections.pairs) return distanceLabel(selections.pairs[0], { condensed: true, unitLabel: this.plugin.managers.structure.measurement.state.options.distanceUnitLabel });
|
||||
if (selections.triples) return angleLabel(selections.triples[0], { condensed: true });
|
||||
if (selections.quads) return dihedralLabel(selections.quads[0], { condensed: true });
|
||||
if (selections.locis) return structureElementLociLabelMany(selections.locis, { countsOnly: true });
|
||||
return '<empty>';
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ const StructureFocusRepresentationParams = (plugin: PluginContext) => {
|
||||
expandRadius: PD.Numeric(5, { min: 1, max: 10, step: 1 }),
|
||||
targetParams: PD.Group(reprParams, {
|
||||
label: 'Target',
|
||||
customDefault: createStructureRepresentationParams(plugin, void 0, { type: 'ball-and-stick', size: 'physical', typeParams: { sizeFactor: 0.26, alpha: 0.51 } })
|
||||
customDefault: createStructureRepresentationParams(plugin, void 0, { type: 'ball-and-stick', size: 'physical', typeParams: { sizeFactor: 0.26, alpha: 0.51, adjustCylinderLength: true } })
|
||||
}),
|
||||
surroundingsParams: PD.Group(reprParams, {
|
||||
label: 'Surroundings',
|
||||
|
||||
@@ -20,9 +20,19 @@ export class PluginConfigItem<T = any> {
|
||||
function item<T>(key: string, defaultValue?: T) { return new PluginConfigItem(key, defaultValue); }
|
||||
|
||||
|
||||
// adapted from https://stackoverflow.com/questions/9038625/detect-if-device-is-ios
|
||||
function is_iOS() {
|
||||
function preferWebGl1() {
|
||||
if (typeof navigator === 'undefined' || typeof window === 'undefined') return false;
|
||||
|
||||
// WebGL2 isn't working in MacOS 12.0.1 Safari 15.1 (but is working in Safari tech preview)
|
||||
// prefer webgl 1 based on the userAgent substring
|
||||
if (navigator.userAgent.indexOf('Version/15.1 Safari') > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for iOS device which enabled WebGL2 recently but it doesn't seem
|
||||
// to be full up to speed yet.
|
||||
|
||||
// adapted from https://stackoverflow.com/questions/9038625/detect-if-device-is-ios
|
||||
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
|
||||
const isAppleDevice = navigator.userAgent.includes('Macintosh');
|
||||
const isTouchScreen = navigator.maxTouchPoints >= 4; // true for iOS 13 (and hopefully beyond)
|
||||
@@ -41,7 +51,7 @@ export const PluginConfig = {
|
||||
EnableWboit: item('plugin-config.enable-wboit', true),
|
||||
// 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', is_iOS()),
|
||||
PreferWebGl1: item('plugin-config.prefer-webgl1', preferWebGl1()),
|
||||
},
|
||||
State: {
|
||||
DefaultServer: item('plugin-state.server', 'https://webchem.ncbr.muni.cz/molstar-state'),
|
||||
|
||||
@@ -65,12 +65,16 @@ export namespace RepresentationProvider {
|
||||
|
||||
export type AnyRepresentationProvider = RepresentationProvider<any, {}, Representation.State>
|
||||
|
||||
const EmptyRepresentationProvider = {
|
||||
export const EmptyRepresentationProvider: RepresentationProvider = {
|
||||
name: '',
|
||||
label: '',
|
||||
description: '',
|
||||
factory: () => Representation.Empty,
|
||||
getParams: () => ({}),
|
||||
defaultValues: {}
|
||||
defaultValues: {},
|
||||
defaultColorTheme: ColorTheme.EmptyProvider,
|
||||
defaultSizeTheme: SizeTheme.EmptyProvider,
|
||||
isApplicable: () => true
|
||||
};
|
||||
|
||||
function getTypes(list: { name: string, provider: RepresentationProvider<any, any, any> }[]) {
|
||||
@@ -114,7 +118,7 @@ export class RepresentationRegistry<D, S extends Representation.State> {
|
||||
}
|
||||
|
||||
get<P extends PD.Params>(name: string): RepresentationProvider<D, P, S> {
|
||||
return this._map.get(name) || EmptyRepresentationProvider as unknown as RepresentationProvider<D, P, S>;
|
||||
return this._map.get(name) || EmptyRepresentationProvider;
|
||||
}
|
||||
|
||||
get list() {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Loci } from '../../../mol-model/loci';
|
||||
import { RuntimeContext } from '../../../mol-task';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { ColorNames } from '../../../mol-util/color/names';
|
||||
@@ -13,21 +12,23 @@ import { Representation, RepresentationParamsGetter, RepresentationContext } fro
|
||||
import { Shape } from '../../../mol-model/shape';
|
||||
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
|
||||
import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { lociLabel } from '../../../mol-theme/label';
|
||||
import { structureElementLociLabelMany } from '../../../mol-theme/label';
|
||||
import { addAxes } from '../../../mol-geo/geometry/mesh/builder/axes';
|
||||
import { addOrientedBox } from '../../../mol-geo/geometry/mesh/builder/box';
|
||||
import { addEllipsoid } from '../../../mol-geo/geometry/mesh/builder/ellipsoid';
|
||||
import { Axes3D } from '../../../mol-math/geometry';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { MarkerActions } from '../../../mol-util/marker-action';
|
||||
import { StructureElement } from '../../../mol-model/structure';
|
||||
|
||||
export interface OrientationData {
|
||||
locis: Loci[]
|
||||
locis: StructureElement.Loci[]
|
||||
}
|
||||
|
||||
const SharedParams = {
|
||||
color: PD.Color(ColorNames.orange),
|
||||
scale: PD.Numeric(2, { min: 0.1, max: 10, step: 0.1 })
|
||||
scaleFactor: PD.Numeric(1, { min: 0.1, max: 10, step: 0.1 }),
|
||||
radiusScale: PD.Numeric(2, { min: 0.1, max: 10, step: 0.1 })
|
||||
};
|
||||
|
||||
const AxesParams = {
|
||||
@@ -57,97 +58,84 @@ const OrientationVisuals = {
|
||||
export const OrientationParams = {
|
||||
...AxesParams,
|
||||
...BoxParams,
|
||||
...EllipsoidParams,
|
||||
visuals: PD.MultiSelect(['box'], PD.objectToOptions(OrientationVisuals)),
|
||||
color: PD.Color(ColorNames.orange),
|
||||
scale: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 })
|
||||
};
|
||||
export type OrientationParams = typeof OrientationParams
|
||||
export type OrientationProps = PD.Values<OrientationParams>
|
||||
|
||||
//
|
||||
|
||||
function orientationLabel(loci: Loci) {
|
||||
const label = lociLabel(loci, { countsOnly: true });
|
||||
function getAxesName(locis: StructureElement.Loci[]) {
|
||||
const label = structureElementLociLabelMany(locis, { countsOnly: true });
|
||||
return `Principal Axes of ${label}`;
|
||||
}
|
||||
|
||||
function getOrientationName(data: OrientationData) {
|
||||
return data.locis.length === 1 ? orientationLabel(data.locis[0]) : `${data.locis.length} Orientations`;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
function buildAxesMesh(data: OrientationData, props: OrientationProps, mesh?: Mesh): Mesh {
|
||||
const state = MeshBuilder.createState(256, 128, mesh);
|
||||
for (let i = 0, il = data.locis.length; i < il; ++i) {
|
||||
const principalAxes = Loci.getPrincipalAxes(data.locis[i]);
|
||||
if (principalAxes) {
|
||||
state.currentGroup = i;
|
||||
addAxes(state, principalAxes.momentsAxes, props.scale, 2, 20);
|
||||
}
|
||||
}
|
||||
const principalAxes = StructureElement.Loci.getPrincipalAxesMany(data.locis);
|
||||
Axes3D.scale(principalAxes.momentsAxes, principalAxes.momentsAxes, props.scaleFactor);
|
||||
|
||||
state.currentGroup = 0;
|
||||
addAxes(state, principalAxes.momentsAxes, props.radiusScale, 2, 20);
|
||||
return MeshBuilder.getMesh(state);
|
||||
}
|
||||
|
||||
function getAxesShape(ctx: RuntimeContext, data: OrientationData, props: OrientationProps, shape?: Shape<Mesh>) {
|
||||
const mesh = buildAxesMesh(data, props, shape && shape.geometry);
|
||||
const name = getOrientationName(data);
|
||||
const getLabel = function (groupId: number) {
|
||||
return orientationLabel(data.locis[groupId]);
|
||||
};
|
||||
return Shape.create(name, data, mesh, () => props.color, () => 1, getLabel);
|
||||
const name = getAxesName(data.locis);
|
||||
return Shape.create(name, data, mesh, () => props.color, () => 1, () => name);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
function getBoxName(locis: StructureElement.Loci[]) {
|
||||
const label = structureElementLociLabelMany(locis, { countsOnly: true });
|
||||
return `Oriented Box of ${label}`;
|
||||
}
|
||||
|
||||
function buildBoxMesh(data: OrientationData, props: OrientationProps, mesh?: Mesh): Mesh {
|
||||
const state = MeshBuilder.createState(256, 128, mesh);
|
||||
for (let i = 0, il = data.locis.length; i < il; ++i) {
|
||||
const principalAxes = Loci.getPrincipalAxes(data.locis[i]);
|
||||
if (principalAxes) {
|
||||
state.currentGroup = i;
|
||||
addOrientedBox(state, principalAxes.boxAxes, props.scale, 2, 20);
|
||||
}
|
||||
}
|
||||
const principalAxes = StructureElement.Loci.getPrincipalAxesMany(data.locis);
|
||||
Axes3D.scale(principalAxes.boxAxes, principalAxes.boxAxes, props.scaleFactor);
|
||||
|
||||
state.currentGroup = 0;
|
||||
addOrientedBox(state, principalAxes.boxAxes, props.radiusScale, 2, 20);
|
||||
return MeshBuilder.getMesh(state);
|
||||
}
|
||||
|
||||
function getBoxShape(ctx: RuntimeContext, data: OrientationData, props: OrientationProps, shape?: Shape<Mesh>) {
|
||||
const mesh = buildBoxMesh(data, props, shape && shape.geometry);
|
||||
const name = getOrientationName(data);
|
||||
const getLabel = function (groupId: number) {
|
||||
return orientationLabel(data.locis[groupId]);
|
||||
};
|
||||
return Shape.create(name, data, mesh, () => props.color, () => 1, getLabel);
|
||||
const name = getBoxName(data.locis);
|
||||
return Shape.create(name, data, mesh, () => props.color, () => 1, () => name);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
function getEllipsoidName(locis: StructureElement.Loci[]) {
|
||||
const label = structureElementLociLabelMany(locis, { countsOnly: true });
|
||||
return `Oriented Ellipsoid of ${label}`;
|
||||
}
|
||||
|
||||
function buildEllipsoidMesh(data: OrientationData, props: OrientationProps, mesh?: Mesh): Mesh {
|
||||
const state = MeshBuilder.createState(256, 128, mesh);
|
||||
for (let i = 0, il = data.locis.length; i < il; ++i) {
|
||||
const principalAxes = Loci.getPrincipalAxes(data.locis[i]);
|
||||
if (principalAxes) {
|
||||
const axes = principalAxes.boxAxes;
|
||||
const { origin, dirA, dirB } = axes;
|
||||
const size = Axes3D.size(Vec3(), axes);
|
||||
Vec3.scale(size, size, 0.5);
|
||||
const radiusScale = Vec3.create(size[2], size[1], size[0]);
|
||||
const principalAxes = StructureElement.Loci.getPrincipalAxesMany(data.locis);
|
||||
|
||||
state.currentGroup = i;
|
||||
addEllipsoid(state, origin, dirA, dirB, radiusScale, 2);
|
||||
}
|
||||
}
|
||||
const axes = principalAxes.boxAxes;
|
||||
const { origin, dirA, dirB } = axes;
|
||||
const size = Axes3D.size(Vec3(), axes);
|
||||
Vec3.scale(size, size, 0.5 * props.scaleFactor);
|
||||
const radiusScale = Vec3.create(size[2], size[1], size[0]);
|
||||
|
||||
state.currentGroup = 0;
|
||||
addEllipsoid(state, origin, dirA, dirB, radiusScale, 2);
|
||||
return MeshBuilder.getMesh(state);
|
||||
}
|
||||
|
||||
function getEllipsoidShape(ctx: RuntimeContext, data: OrientationData, props: OrientationProps, shape?: Shape<Mesh>) {
|
||||
const mesh = buildEllipsoidMesh(data, props, shape && shape.geometry);
|
||||
const name = getOrientationName(data);
|
||||
const getLabel = function (groupId: number) {
|
||||
return orientationLabel(data.locis[groupId]);
|
||||
};
|
||||
return Shape.create(name, data, mesh, () => props.color, () => 1, getLabel);
|
||||
const name = getEllipsoidName(data.locis);
|
||||
return Shape.create(name, data, mesh, () => props.color, () => 1, () => name);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
84
src/mol-repr/shape/loci/plane.ts
Normal file
84
src/mol-repr/shape/loci/plane.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { RuntimeContext } from '../../../mol-task';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { ColorNames } from '../../../mol-util/color/names';
|
||||
import { ShapeRepresentation } from '../representation';
|
||||
import { Representation, RepresentationParamsGetter, RepresentationContext } from '../../representation';
|
||||
import { Shape } from '../../../mol-model/shape';
|
||||
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
|
||||
import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { structureElementLociLabelMany } from '../../../mol-theme/label';
|
||||
import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { MarkerActions } from '../../../mol-util/marker-action';
|
||||
import { Plane } from '../../../mol-geo/primitive/plane';
|
||||
import { StructureElement } from '../../../mol-model/structure';
|
||||
import { Axes3D } from '../../../mol-math/geometry';
|
||||
|
||||
export interface PlaneData {
|
||||
locis: StructureElement.Loci[]
|
||||
}
|
||||
|
||||
const _PlaneParams = {
|
||||
...Mesh.Params,
|
||||
color: PD.Color(ColorNames.orange),
|
||||
scaleFactor: PD.Numeric(1, { min: 0.1, max: 10, step: 0.1 }),
|
||||
};
|
||||
type _PlaneParams = typeof _PlaneParams
|
||||
|
||||
const PlaneVisuals = {
|
||||
'plane': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<PlaneData, _PlaneParams>) => ShapeRepresentation(getPlaneShape, Mesh.Utils),
|
||||
};
|
||||
|
||||
export const PlaneParams = {
|
||||
..._PlaneParams,
|
||||
visuals: PD.MultiSelect(['plane'], PD.objectToOptions(PlaneVisuals)),
|
||||
};
|
||||
export type PlaneParams = typeof PlaneParams
|
||||
export type PlaneProps = PD.Values<PlaneParams>
|
||||
|
||||
//
|
||||
|
||||
function getPlaneName(locis: StructureElement.Loci[]) {
|
||||
const label = structureElementLociLabelMany(locis, { countsOnly: true });
|
||||
return `Best Fit Plane of ${label}`;
|
||||
}
|
||||
|
||||
const tmpMat = Mat4();
|
||||
const tmpV = Vec3();
|
||||
function buildPlaneMesh(data: PlaneData, props: PlaneProps, mesh?: Mesh): Mesh {
|
||||
const state = MeshBuilder.createState(256, 128, mesh);
|
||||
const principalAxes = StructureElement.Loci.getPrincipalAxesMany(data.locis);
|
||||
const axes = principalAxes.boxAxes;
|
||||
const plane = Plane();
|
||||
|
||||
Vec3.add(tmpV, axes.origin, axes.dirC);
|
||||
Mat4.targetTo(tmpMat, tmpV, axes.origin, axes.dirB);
|
||||
Mat4.scale(tmpMat, tmpMat, Axes3D.size(tmpV, axes));
|
||||
Mat4.scaleUniformly(tmpMat, tmpMat, props.scaleFactor);
|
||||
Mat4.setTranslation(tmpMat, axes.origin);
|
||||
|
||||
state.currentGroup = 0;
|
||||
MeshBuilder.addPrimitive(state, tmpMat, plane);
|
||||
MeshBuilder.addPrimitiveFlipped(state, tmpMat, plane);
|
||||
return MeshBuilder.getMesh(state);
|
||||
}
|
||||
|
||||
function getPlaneShape(ctx: RuntimeContext, data: PlaneData, props: PlaneProps, shape?: Shape<Mesh>) {
|
||||
const mesh = buildPlaneMesh(data, props, shape && shape.geometry);
|
||||
const name = getPlaneName(data.locis);
|
||||
return Shape.create(name, data, mesh, () => props.color, () => 1, () => name);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export type PlaneRepresentation = Representation<PlaneData, PlaneParams>
|
||||
export function PlaneRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<PlaneData, PlaneParams>): PlaneRepresentation {
|
||||
const repr = Representation.createMulti('Plane', ctx, getParams, Representation.StateBuilder, PlaneVisuals as unknown as Representation.Def<PlaneData, PlaneParams>);
|
||||
repr.setState({ markerActions: MarkerActions.Highlighting });
|
||||
return repr;
|
||||
}
|
||||
@@ -215,6 +215,9 @@ export function InterUnitBondCylinderImpostorVisual(materialId: number): Complex
|
||||
newProps.linkSpacing !== currentProps.linkSpacing ||
|
||||
newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
|
||||
newProps.linkCap !== currentProps.linkCap ||
|
||||
newProps.aromaticScale !== currentProps.aromaticScale ||
|
||||
newProps.aromaticSpacing !== currentProps.aromaticSpacing ||
|
||||
newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
|
||||
newProps.dashCount !== currentProps.dashCount ||
|
||||
newProps.dashScale !== currentProps.dashScale ||
|
||||
newProps.dashCap !== currentProps.dashCap ||
|
||||
@@ -254,6 +257,9 @@ export function InterUnitBondCylinderMeshVisual(materialId: number): ComplexVisu
|
||||
newProps.linkSpacing !== currentProps.linkSpacing ||
|
||||
newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
|
||||
newProps.linkCap !== currentProps.linkCap ||
|
||||
newProps.aromaticScale !== currentProps.aromaticScale ||
|
||||
newProps.aromaticSpacing !== currentProps.aromaticSpacing ||
|
||||
newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
|
||||
newProps.dashCount !== currentProps.dashCount ||
|
||||
newProps.dashScale !== currentProps.dashScale ||
|
||||
newProps.dashCap !== currentProps.dashCap ||
|
||||
|
||||
@@ -131,6 +131,7 @@ export function InterUnitBondLineVisual(materialId: number): ComplexVisual<Inter
|
||||
newProps.sizeFactor !== currentProps.sizeFactor ||
|
||||
newProps.linkScale !== currentProps.linkScale ||
|
||||
newProps.linkSpacing !== currentProps.linkSpacing ||
|
||||
newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
|
||||
newProps.dashCount !== currentProps.dashCount ||
|
||||
newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
|
||||
!arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
|
||||
|
||||
@@ -228,6 +228,9 @@ export function IntraUnitBondCylinderImpostorVisual(materialId: number): UnitsVi
|
||||
newProps.linkSpacing !== currentProps.linkSpacing ||
|
||||
newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
|
||||
newProps.linkCap !== currentProps.linkCap ||
|
||||
newProps.aromaticScale !== currentProps.aromaticScale ||
|
||||
newProps.aromaticSpacing !== currentProps.aromaticSpacing ||
|
||||
newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
|
||||
newProps.dashCount !== currentProps.dashCount ||
|
||||
newProps.dashScale !== currentProps.dashScale ||
|
||||
newProps.dashCap !== currentProps.dashCap ||
|
||||
@@ -272,6 +275,9 @@ export function IntraUnitBondCylinderMeshVisual(materialId: number): UnitsVisual
|
||||
newProps.linkSpacing !== currentProps.linkSpacing ||
|
||||
newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
|
||||
newProps.linkCap !== currentProps.linkCap ||
|
||||
newProps.aromaticScale !== currentProps.aromaticScale ||
|
||||
newProps.aromaticSpacing !== currentProps.aromaticSpacing ||
|
||||
newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
|
||||
newProps.dashCount !== currentProps.dashCount ||
|
||||
newProps.dashScale !== currentProps.dashScale ||
|
||||
newProps.dashCap !== currentProps.dashCap ||
|
||||
|
||||
@@ -153,6 +153,7 @@ export function IntraUnitBondLineVisual(materialId: number): UnitsVisual<IntraUn
|
||||
newProps.sizeFactor !== currentProps.sizeFactor ||
|
||||
newProps.linkScale !== currentProps.linkScale ||
|
||||
newProps.linkSpacing !== currentProps.linkSpacing ||
|
||||
newProps.aromaticDashCount !== currentProps.aromaticDashCount ||
|
||||
newProps.dashCount !== currentProps.dashCount ||
|
||||
newProps.ignoreHydrogens !== currentProps.ignoreHydrogens ||
|
||||
!arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
|
||||
|
||||
@@ -28,7 +28,7 @@ export type BondProps = typeof DefaultBondProps
|
||||
export const BondCylinderParams = {
|
||||
...LinkCylinderParams,
|
||||
...BondParams,
|
||||
adjustCylinderLength: PD.Boolean(true, { description: 'Shorten cylinders to reduce overlap with spheres.' })
|
||||
adjustCylinderLength: PD.Boolean(false, { description: 'Shorten cylinders to reduce overlap with spheres. Useful for for transparent bonds. Not working well with aromatic bonds.' })
|
||||
};
|
||||
export const DefaultBondCylinderProps = PD.getDefaultValues(BondCylinderParams);
|
||||
export type BondCylinderProps = typeof DefaultBondCylinderProps
|
||||
|
||||
@@ -21,6 +21,9 @@ export const LinkCylinderParams = {
|
||||
linkScale: PD.Numeric(0.45, { min: 0, max: 1, step: 0.01 }),
|
||||
linkSpacing: PD.Numeric(1, { min: 0, max: 2, step: 0.01 }),
|
||||
linkCap: PD.Boolean(false),
|
||||
aromaticScale: PD.Numeric(0.3, { min: 0, max: 1, step: 0.01 }),
|
||||
aromaticSpacing: PD.Numeric(1.5, { min: 0, max: 3, step: 0.01 }),
|
||||
aromaticDashCount: PD.Numeric(2, { min: 2, max: 6, step: 2 }),
|
||||
dashCount: PD.Numeric(4, { min: 2, max: 10, step: 2 }),
|
||||
dashScale: PD.Numeric(0.8, { min: 0, max: 2, step: 0.1 }),
|
||||
dashCap: PD.Boolean(true),
|
||||
@@ -33,6 +36,7 @@ export type LinkCylinderProps = typeof DefaultLinkCylinderProps
|
||||
export const LinkLineParams = {
|
||||
linkScale: PD.Numeric(0.5, { min: 0, max: 1, step: 0.1 }),
|
||||
linkSpacing: PD.Numeric(0.1, { min: 0, max: 2, step: 0.01 }),
|
||||
aromaticDashCount: PD.Numeric(2, { min: 2, max: 6, step: 2 }),
|
||||
dashCount: PD.Numeric(4, { min: 2, max: 10, step: 2 }),
|
||||
};
|
||||
export const DefaultLinkLineProps = PD.getDefaultValues(LinkLineParams);
|
||||
@@ -107,7 +111,7 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
|
||||
|
||||
if (!linkCount) return Mesh.createEmpty(mesh);
|
||||
|
||||
const { linkScale, linkSpacing, radialSegments, linkCap, dashCount, dashScale, dashCap, stubCap } = props;
|
||||
const { linkScale, linkSpacing, radialSegments, linkCap, aromaticScale, aromaticSpacing, aromaticDashCount, dashCount, dashScale, dashCap, stubCap } = props;
|
||||
|
||||
const vertexCountEstimate = radialSegments * 2 * linkCount * 2;
|
||||
const builderState = MeshBuilder.createState(vertexCountEstimate, vertexCountEstimate / 4, mesh);
|
||||
@@ -138,8 +142,7 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
|
||||
const [topCap, bottomCap] = dirFlag ? [linkStub, linkCap] : [linkCap, linkStub];
|
||||
builderState.currentGroup = edgeIndex;
|
||||
|
||||
const aromaticOffsetFactor = 5.5;
|
||||
const multipleOffsetFactor = 4;
|
||||
const aromaticSegmentCount = aromaticDashCount + 1;
|
||||
|
||||
if (linkStyle === LinkStyle.Solid) {
|
||||
cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
|
||||
@@ -153,8 +156,8 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
|
||||
|
||||
addFixedCountDashedCylinder(builderState, va, vb, 0.5, segmentCount, cylinderProps);
|
||||
} else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
|
||||
const order = linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble ? 2 :
|
||||
linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple ? 3 : 1.5;
|
||||
const order = (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble) ? 2 :
|
||||
(linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple) ? 3 : 1.5;
|
||||
const multiRadius = linkRadius * (linkScale / (0.5 * order));
|
||||
const absOffset = (linkRadius - multiRadius) * linkSpacing;
|
||||
|
||||
@@ -167,21 +170,28 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
|
||||
cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
|
||||
addCylinder(builderState, va, vb, 0.5, cylinderProps);
|
||||
|
||||
cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius * linkScale;
|
||||
const aromaticOffset = linkRadius + aromaticScale * linkRadius + aromaticScale * linkRadius * aromaticSpacing;
|
||||
|
||||
v3setMagnitude(tmpV12, v3sub(tmpV12, vb, va), linkRadius * 0.5);
|
||||
v3add(va, va, tmpV12);
|
||||
v3sub(vb, vb, tmpV12);
|
||||
|
||||
cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius * aromaticScale;
|
||||
cylinderProps.topCap = cylinderProps.bottomCap = dashCap;
|
||||
v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor);
|
||||
v3setMagnitude(vShift, vShift, aromaticOffset);
|
||||
v3sub(va, va, vShift);
|
||||
v3sub(vb, vb, vShift);
|
||||
addFixedCountDashedCylinder(builderState, va, vb, 0.5, 3, cylinderProps);
|
||||
addFixedCountDashedCylinder(builderState, va, vb, 0.5, aromaticSegmentCount, cylinderProps);
|
||||
|
||||
if (linkStyle === LinkStyle.MirroredAromatic) {
|
||||
v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor * 2);
|
||||
v3setMagnitude(vShift, vShift, aromaticOffset * 2);
|
||||
v3add(va, va, vShift);
|
||||
v3add(vb, vb, vShift);
|
||||
addFixedCountDashedCylinder(builderState, va, vb, 0.5, 3, cylinderProps);
|
||||
addFixedCountDashedCylinder(builderState, va, vb, 0.5, aromaticSegmentCount, cylinderProps);
|
||||
}
|
||||
} else if (linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.OffsetTriple) {
|
||||
v3setMagnitude(vShift, vShift, absOffset);
|
||||
const multipleOffset = linkRadius + multiRadius + linkScale * linkRadius * linkSpacing;
|
||||
v3setMagnitude(vShift, vShift, multipleOffset);
|
||||
|
||||
cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius;
|
||||
addCylinder(builderState, va, vb, 0.5, cylinderProps);
|
||||
@@ -193,13 +203,13 @@ export function createLinkCylinderMesh(ctx: VisualContext, linkBuilder: LinkBuil
|
||||
cylinderProps.radiusTop = cylinderProps.radiusBottom = multiRadius;
|
||||
cylinderProps.topCap = dirFlag ? linkStub : dashCap;
|
||||
cylinderProps.bottomCap = dirFlag ? dashCap : linkStub;
|
||||
v3setMagnitude(vShift, vShift, absOffset * multipleOffsetFactor);
|
||||
v3setMagnitude(vShift, vShift, multipleOffset);
|
||||
v3sub(va, va, vShift);
|
||||
v3sub(vb, vb, vShift);
|
||||
addCylinder(builderState, va, vb, 0.5, cylinderProps);
|
||||
|
||||
if (order === 3) {
|
||||
v3setMagnitude(vShift, vShift, absOffset * multipleOffsetFactor * 2);
|
||||
v3setMagnitude(vShift, vShift, multipleOffset * 2);
|
||||
v3add(va, va, vShift);
|
||||
v3add(vb, vb, vShift);
|
||||
addCylinder(builderState, va, vb, 0.5, cylinderProps);
|
||||
@@ -236,7 +246,7 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
|
||||
|
||||
if (!linkCount) return Cylinders.createEmpty(cylinders);
|
||||
|
||||
const { linkScale, linkSpacing, linkCap, dashCount, dashScale, dashCap, stubCap } = props;
|
||||
const { linkScale, linkSpacing, linkCap, aromaticScale, aromaticSpacing, aromaticDashCount, dashCount, dashScale, dashCap, stubCap } = props;
|
||||
|
||||
const cylindersCountEstimate = linkCount * 2;
|
||||
const builder = CylindersBuilder.create(cylindersCountEstimate, cylindersCountEstimate / 4, cylinders);
|
||||
@@ -250,10 +260,8 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
|
||||
const segmentCount = dashCount % 2 === 1 ? dashCount : dashCount + 1;
|
||||
const lengthScale = 0.5 - (0.5 / 2 / segmentCount);
|
||||
|
||||
const aromaticSegmentCount = 3;
|
||||
const aromaticSegmentCount = aromaticDashCount + 1;
|
||||
const aromaticLengthScale = 0.5 - (0.5 / 2 / aromaticSegmentCount);
|
||||
const aromaticOffsetFactor = 5.5;
|
||||
const multipleOffsetFactor = 4;
|
||||
|
||||
for (let edgeIndex = 0, _eI = linkCount; edgeIndex < _eI; ++edgeIndex) {
|
||||
if (ignore && ignore(edgeIndex)) continue;
|
||||
@@ -272,8 +280,8 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
|
||||
v3sub(vb, vb, tmpV12);
|
||||
builder.addFixedCountDashes(va, vb, segmentCount, dashScale, dashCap, dashCap, edgeIndex);
|
||||
} else if (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple || linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
|
||||
const order = linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble ? 2 :
|
||||
linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple ? 3 : 1.5;
|
||||
const order = (linkStyle === LinkStyle.Double || linkStyle === LinkStyle.OffsetDouble) ? 2 :
|
||||
(linkStyle === LinkStyle.Triple || linkStyle === LinkStyle.OffsetTriple) ? 3 : 1.5;
|
||||
const multiScale = linkScale / (0.5 * order);
|
||||
const absOffset = (linkRadius - multiScale * linkRadius) * linkSpacing;
|
||||
|
||||
@@ -283,26 +291,32 @@ export function createLinkCylinderImpostors(ctx: VisualContext, linkBuilder: Lin
|
||||
if (linkStyle === LinkStyle.Aromatic || linkStyle === LinkStyle.MirroredAromatic) {
|
||||
builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], 1, linkCap, linkStub, edgeIndex);
|
||||
|
||||
const aromaticOffset = linkRadius + aromaticScale * linkRadius + aromaticScale * linkRadius * aromaticSpacing;
|
||||
|
||||
v3scale(tmpV12, v3sub(tmpV12, vb, va), aromaticLengthScale);
|
||||
v3sub(vb, vb, tmpV12);
|
||||
|
||||
v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor);
|
||||
v3setMagnitude(tmpV12, v3sub(tmpV12, vb, va), linkRadius * 0.5);
|
||||
v3add(va, va, tmpV12);
|
||||
|
||||
v3setMagnitude(vShift, vShift, aromaticOffset);
|
||||
v3sub(va, va, vShift);
|
||||
v3sub(vb, vb, vShift);
|
||||
builder.addFixedCountDashes(va, vb, aromaticSegmentCount, linkScale, dashCap, dashCap, edgeIndex);
|
||||
builder.addFixedCountDashes(va, vb, aromaticSegmentCount, aromaticScale, dashCap, dashCap, edgeIndex);
|
||||
|
||||
if (linkStyle === LinkStyle.MirroredAromatic) {
|
||||
v3setMagnitude(vShift, vShift, absOffset * aromaticOffsetFactor * 2);
|
||||
v3setMagnitude(vShift, vShift, aromaticOffset * 2);
|
||||
v3add(va, va, vShift);
|
||||
v3add(vb, vb, vShift);
|
||||
builder.addFixedCountDashes(va, vb, aromaticSegmentCount, linkScale, dashCap, dashCap, edgeIndex);
|
||||
builder.addFixedCountDashes(va, vb, aromaticSegmentCount, aromaticScale, dashCap, dashCap, edgeIndex);
|
||||
}
|
||||
} else if (linkStyle === LinkStyle.OffsetDouble || linkStyle === LinkStyle.OffsetTriple) {
|
||||
v3setMagnitude(vShift, vShift, absOffset * multipleOffsetFactor);
|
||||
const multipleOffset = linkRadius + multiScale * linkRadius + linkScale * linkRadius * linkSpacing;
|
||||
v3setMagnitude(vShift, vShift, multipleOffset);
|
||||
|
||||
builder.add(va[0], va[1], va[2], vm[0], vm[1], vm[2], 1, linkCap, linkStub, edgeIndex);
|
||||
|
||||
v3scale(tmpV12, v3sub(tmpV12, va, vb), linkSpacing * linkScale * 0.2);
|
||||
v3setMagnitude(tmpV12, v3sub(tmpV12, va, vb), linkRadius / 1.5);
|
||||
v3sub(va, va, tmpV12);
|
||||
|
||||
if (order === 3) builder.add(va[0] + vShift[0], va[1] + vShift[1], va[2] + vShift[2], vm[0] + vShift[0], vm[1] + vShift[1], vm[2] + vShift[2], multiScale, linkCap, linkStub, edgeIndex);
|
||||
@@ -335,7 +349,7 @@ export function createLinkLines(ctx: VisualContext, linkBuilder: LinkBuilderProp
|
||||
|
||||
if (!linkCount) return Lines.createEmpty(lines);
|
||||
|
||||
const { linkScale, linkSpacing, dashCount } = props;
|
||||
const { linkScale, linkSpacing, aromaticDashCount, dashCount } = props;
|
||||
|
||||
const linesCountEstimate = linkCount * 2;
|
||||
const builder = LinesBuilder.create(linesCountEstimate, linesCountEstimate / 4, lines);
|
||||
@@ -349,7 +363,7 @@ export function createLinkLines(ctx: VisualContext, linkBuilder: LinkBuilderProp
|
||||
const segmentCount = dashCount % 2 === 1 ? dashCount : dashCount + 1;
|
||||
const lengthScale = 0.5 - (0.5 / 2 / segmentCount);
|
||||
|
||||
const aromaticSegmentCount = 3;
|
||||
const aromaticSegmentCount = aromaticDashCount + 1;
|
||||
const aromaticLengthScale = 0.5 - (0.5 / 2 / aromaticSegmentCount);
|
||||
const aromaticOffsetFactor = 4.5;
|
||||
const multipleOffsetFactor = 3;
|
||||
|
||||
@@ -92,6 +92,14 @@ export function structureElementStatsLabel(stats: StructureElement.Stats, option
|
||||
return o.htmlStyling ? label : stripTags(label);
|
||||
}
|
||||
|
||||
export function structureElementLociLabelMany(locis: StructureElement.Loci[], options: Partial<LabelOptions> = {}): string {
|
||||
const stats = StructureElement.Stats.create();
|
||||
for (const l of locis) {
|
||||
StructureElement.Stats.add(stats, stats, StructureElement.Stats.ofLoci(l));
|
||||
}
|
||||
return structureElementStatsLabel(stats, options);
|
||||
}
|
||||
|
||||
function _structureElementStatsLabel(stats: StructureElement.Stats, countsOnly = false, hidePrefix = false, condensed = false, reverse = false): string {
|
||||
const { structureCount, chainCount, residueCount, conformationCount, elementCount } = stats;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"noEmitHelpers": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"jsx": "react-jsx",
|
||||
"lib": [ "es6", "dom", "esnext.asynciterable", "es2016" ],
|
||||
"rootDir": "src",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"noEmitHelpers": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"jsx": "react-jsx",
|
||||
"lib": [ "es6", "dom", "esnext.asynciterable", "es2016" ],
|
||||
"rootDir": "src",
|
||||
|
||||
Reference in New Issue
Block a user