Compare commits

...

14 Commits

Author SHA1 Message Date
Alexander Rose
fe47134934 0.5.2 2020-02-25 12:00:59 -08:00
Alexander Rose
131cc606f0 added support to build custom assembly from symmetry operations and asym ids 2020-02-25 11:57:58 -08:00
Alexander Rose
3681f01fad added Spacegroup.getOperatorXyz 2020-02-25 11:56:48 -08:00
Alexander Rose
e966c112ab assymbly-symmetry: filter C1, export AssemblySymmetry3D 2020-02-24 17:25:49 -08:00
Alexander Rose
7986509ad3 0.5.1 2020-02-21 16:54:42 -08:00
Alexander Rose
73dcf970f3 fix fog handling so fog can be fully switched off 2020-02-21 16:26:14 -08:00
Alexander Rose
9377aa2d05 renamed renderstyle 'toon' to 'flat'
- reflects what the options is actually doing
2020-02-21 16:05:35 -08:00
Alexander Rose
686fa5a5ed package updates 2020-02-21 16:05:01 -08:00
Alexander Rose
c946ae6eab updated schemas, emmit .ts instead of .d.ts 2020-02-21 16:01:41 -08:00
David Sehnal
9b2f1d9415 updated to TypeScript 3.8 2020-02-21 17:13:10 +01:00
Alexander Rose
f6c28aa8e2 0.5.0 2020-02-20 16:25:38 -08:00
Alexander Rose
64c72aa6f5 0.5.0-dev.3 2020-02-20 10:56:21 -08:00
Alexander Rose
0a22917773 add unknown/any DNA/RNA base names N/DN 2020-02-20 10:23:39 -08:00
David Sehnal
3c4888e52b mol-canvas3d: tweak commit and camera reset 2020-02-20 12:55:21 +01:00
23 changed files with 468 additions and 991 deletions

View File

@@ -1,7 +1,7 @@
schema: https://data-beta.rcsb.org/graphql
documents: './src/mol-model-props/rcsb/graphql/symmetry.gql.ts'
generates:
'./src/mol-model-props/rcsb/graphql/types.d.ts':
'./src/mol-model-props/rcsb/graphql/types.ts':
plugins:
- add: '/* eslint-disable */'
- time

1086
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "0.5.0-dev.2",
"version": "0.5.2",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -72,9 +72,9 @@
"@graphql-codegen/typescript-graphql-request": "^1.12.2",
"@graphql-codegen/typescript-operations": "^1.12.2",
"@types/cors": "^2.8.6",
"@typescript-eslint/eslint-plugin": "^2.19.2",
"@typescript-eslint/eslint-plugin-tslint": "^2.19.2",
"@typescript-eslint/parser": "^2.19.2",
"@typescript-eslint/eslint-plugin": "^2.20.0",
"@typescript-eslint/eslint-plugin-tslint": "^2.20.0",
"@typescript-eslint/parser": "^2.20.0",
"benchmark": "^2.1.4",
"circular-dependency-plugin": "^5.2.0",
"concurrently": "^5.1.0",
@@ -82,7 +82,7 @@
"css-loader": "^3.4.2",
"eslint": "^6.8.0",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^5.0.2",
"file-loader": "^5.1.0",
"fs-extra": "^8.1.0",
"http-server": "^0.12.1",
"jest": "^25.1.0",
@@ -95,20 +95,20 @@
"sass-loader": "^8.0.2",
"simple-git": "^1.131.0",
"style-loader": "^1.1.3",
"ts-jest": "^25.2.0",
"typescript": "^3.7.5",
"webpack": "^4.41.5",
"webpack-cli": "^3.3.10"
"ts-jest": "^25.2.1",
"typescript": "^3.8.2",
"webpack": "^4.41.6",
"webpack-cli": "^3.3.11"
},
"dependencies": {
"@types/argparse": "^1.0.38",
"@types/benchmark": "^1.0.31",
"@types/compression": "1.7.0",
"@types/express": "^4.17.2",
"@types/jest": "^25.1.2",
"@types/node": "^13.7.0",
"@types/jest": "^25.1.3",
"@types/node": "^13.7.4",
"@types/node-fetch": "^2.5.4",
"@types/react": "^16.9.19",
"@types/react": "^16.9.22",
"@types/react-dom": "^16.9.5",
"@types/swagger-ui-dist": "3.0.5",
"argparse": "^1.0.10",

View File

@@ -144,9 +144,9 @@ async function createBonds() {
const comp_id: string[] = []
const atom_id_1: string[] = []
const atom_id_2: string[] = []
const value_order: string[] = []
const pdbx_aromatic_flag: string[] = []
const pdbx_stereo_config: string[] = []
const value_order: typeof mmCIF_chemCompBond_schema['value_order']['T'][] = []
const pdbx_aromatic_flag: typeof mmCIF_chemCompBond_schema['pdbx_aromatic_flag']['T'][] = []
const pdbx_stereo_config: typeof mmCIF_chemCompBond_schema['pdbx_stereo_config']['T'][] = []
const molstar_protonation_variant: string[] = []
function addBonds(compId: string, ccb: CCB, protonationVariant: boolean) {

View File

@@ -264,12 +264,13 @@ function updatePers(camera: Camera) {
function updateClip(camera: Camera) {
const { radiusNear, radiusFar, mode, fog, clipFar } = camera.state
const cDist = Vec3.distance(camera.position, camera.target)
let near = cDist - radiusNear
let far = cDist + (clipFar ? radiusNear : radiusFar)
const normalizedFar = clipFar ? radiusNear : radiusFar
const cameraDistance = Vec3.distance(camera.position, camera.target)
let near = cameraDistance - radiusNear
let far = cameraDistance + normalizedFar
const fogNearFactor = -(50 - fog) / 50
let fogNear = cDist - (radiusNear * fogNearFactor)
let fogNear = cameraDistance - (normalizedFar * fogNearFactor)
let fogFar = far
if (mode === 'perspective') {

View File

@@ -38,7 +38,7 @@ import { isDebugMode } from '../mol-util/debug';
export const Canvas3DParams = {
cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']]),
cameraFog: PD.Numeric(50, { min: 1, max: 100, step: 1 }),
cameraFog: PD.Numeric(50, { min: 0, max: 100, step: 1 }),
cameraClipFar: PD.Boolean(true),
cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }),
transparentBackground: PD.Boolean(false),
@@ -62,7 +62,7 @@ interface Canvas3D {
/**
* This function must be called if animate() is not set up so that add/remove actions take place.
*/
commit(): void
commit(isSynchronous?: boolean): void
update(repr?: Representation.Any, keepBoundingSphere?: boolean): void
clear(): void
@@ -77,7 +77,7 @@ interface Canvas3D {
handleResize(): void
/** Focuses camera on scene's bounding sphere, centered and zoomed. */
requestCameraReset(): void
requestCameraReset(durationMs?: number): void
readonly camera: Camera
readonly boundingSphere: Readonly<Sphere3D>
downloadScreenshot(): void
@@ -190,6 +190,7 @@ namespace Canvas3D {
let drawPending = false
let cameraResetRequested = false
let nextCameraResetDuration: number | undefined = void 0
function getLoci(pickingId: PickingId) {
let loci: Loci = EmptyLoci
@@ -266,9 +267,7 @@ namespace Canvas3D {
function animate() {
currentTime = now();
commit();
camera.transition.tick(currentTime);
draw(false);
@@ -282,25 +281,30 @@ namespace Canvas3D {
return webgl.isContextLost ? undefined : pickPass.identify(x, y)
}
function commit() {
commitScene();
resolveCameraReset();
function commit(isSynchronous: boolean = false) {
const allCommited = commitScene(isSynchronous);
// Only reset the camera after the full scene has been commited.
if (allCommited) resolveCameraReset();
}
function resolveCameraReset() {
if (!cameraResetRequested) return;
const { center, radius } = scene.boundingSphere;
camera.focus(center, radius, radius, p.cameraResetDurationMs);
const duration = nextCameraResetDuration === undefined ? p.cameraResetDurationMs : nextCameraResetDuration
camera.focus(center, radius, radius, duration);
nextCameraResetDuration = void 0;
cameraResetRequested = false;
}
const sceneCommitTimeoutMs = 250;
function commitScene() {
if (!scene.needsCommit) return;
function commitScene(isSynchronous: boolean) {
if (!scene.needsCommit) return true;
if (!scene.commit(isSynchronous ? void 0 : sceneCommitTimeoutMs)) return false;
const allCommited = scene.commit(sceneCommitTimeoutMs);
if (debugHelper.isEnabled) debugHelper.update();
if (allCommited) reprCount.next(reprRenderObjects.size);
reprCount.next(reprRenderObjects.size);
return true;
}
function add(repr: Representation.Any) {
@@ -384,7 +388,8 @@ namespace Canvas3D {
getLoci,
handleResize,
requestCameraReset: () => {
requestCameraReset: (durationMs) => {
nextCameraResetDuration = durationMs;
cameraResetRequested = true;
},
camera,

View File

@@ -9,7 +9,7 @@ import { sortArray } from '../util/sort'
import { StringBuilder } from '../../mol-util';
/** A collection of columns */
type Table<Schema extends Table.Schema> = {
type Table<Schema extends Table.Schema = any> = {
readonly _rowCount: number,
readonly _columns: ReadonlyArray<string>,
readonly _schema: Schema
@@ -73,7 +73,7 @@ namespace Table {
return ret;
}
export function ofRows<S extends Schema, R extends Table<S> = Table<S>>(schema: Schema, rows: ArrayLike<Partial<Row<S>>>): R {
export function ofRows<S extends Schema, R extends Table<S> = Table<S>>(schema: S, rows: ArrayLike<Partial<Row<S>>>): R {
const ret = Object.create(null);
const rowCount = rows.length;
const columns = Object.keys(schema);
@@ -91,14 +91,19 @@ namespace Table {
return ret as R;
}
export function ofArrays<S extends Schema, R extends Table<S> = Table<S>>(schema: Schema, arrays: Arrays<S>): R {
export function ofArrays<S extends Schema, R extends Table<S> = Table<S>>(schema: S, arrays: Partial<Arrays<S>>): R {
const ret = Object.create(null);
const columns = Object.keys(schema);
ret._rowCount = arrays[columns[0]].length;
ret._rowCount = 0;
ret._columns = columns;
ret._schema = schema;
for (const k of columns) {
(ret as any)[k] = typeof arrays[k] !== 'undefined' ? Column.ofArray({ array: arrays[k], schema: schema[k] }) : Column.Undefined(ret._rowCount, schema[k]);
if (typeof arrays[k] !== 'undefined') {
(ret as any)[k] = Column.ofArray({ array: arrays[k]!, schema: schema[k] });
ret._rowCount = arrays[k]?.length;
} else {
(ret as any)[k] = Column.Undefined(ret._rowCount, schema[k]);
}
}
return ret as R;
}
@@ -167,7 +172,7 @@ namespace Table {
}
/** Sort and return a new table */
export function sort<T extends Table<S>, S extends Schema>(table: T, cmp: (i: number, j: number) => number) {
export function sort<T extends Table>(table: T, cmp: (i: number, j: number) => number) {
const indices = new Int32Array(table._rowCount);
for (let i = 0, _i = indices.length; i < _i; i++) indices[i] = i;
sortArray(indices, (_, i, j) => cmp(i, j));
@@ -191,7 +196,7 @@ namespace Table {
return ret;
}
export function areEqual<T extends Table<Schema>>(a: T, b: T) {
export function areEqual<T extends Table<any>>(a: T, b: T) {
if (a._rowCount !== b._rowCount) return false;
if (a._columns.length !== b._columns.length) return false;
for (const c of a._columns) {

View File

@@ -126,8 +126,6 @@ namespace Scene {
return true;
}
// const toAdd: GraphicsRenderObject[] = []
// const toRemove: GraphicsRenderObject[] = []
const commitQueue = new CommitQueue();
return {

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.323, IHM 1.07, CARB draft.
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.323, IHM 1.08, CARB draft.
*
* @author molstar/ciftools package
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.323, IHM 1.07, CARB draft.
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.323, IHM 1.08, CARB draft.
*
* @author molstar/ciftools package
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.323, IHM 1.07, CARB draft.
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.323, IHM 1.08, CARB draft.
*
* @author molstar/ciftools package
*/

View File

@@ -199,7 +199,7 @@ export namespace Category {
getFormat(cat, field) { return void 0; }
}
export function ofTable(table: Table<Table.Schema>, indices?: ArrayLike<number>): Category.Instance {
export function ofTable(table: Table, indices?: ArrayLike<number>): Category.Instance {
if (indices) {
return {
fields: cifFieldsFromTableSchema(table._schema),

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Spacegroup, SpacegroupCell } from '../spacegroup/construction';
import { Vec3 } from '../../linear-algebra';
function getSpacegroup(name: string) {
const size = Vec3.create(1, 1, 1)
const anglesInRadians = Vec3.create(Math.PI / 2, Math.PI / 2, Math.PI / 2)
const cell = SpacegroupCell.create(name, size, anglesInRadians)
return Spacegroup.create(cell)
}
function checkOperatorsXyz(name: string, expected: string[]) {
const spacegroup = getSpacegroup(name)
for (let i = 0, il = spacegroup.operators.length; i < il; ++i) {
const op = spacegroup.operators[i]
const actual = Spacegroup.getOperatorXyz(op)
expect(actual).toBe(expected[i])
}
}
describe('Spacegroup', () => {
it('operators xyz', () => {
checkOperatorsXyz('P 1', ['X,Y,Z'])
checkOperatorsXyz('P -1', ['X,Y,Z', '-X,-Y,-Z'])
checkOperatorsXyz('P 1 21 1', ['X,Y,Z', '-X,1/2+Y,-Z'])
checkOperatorsXyz('P 1 21/m 1', ['X,Y,Z', '-X,1/2+Y,-Z', '-X,-Y,-Z', 'X,1/2-Y,Z'])
checkOperatorsXyz('P 41', ['X,Y,Z', '-X,-Y,1/2+Z', '-Y,X,1/4+Z', 'Y,-X,3/4+Z'])
checkOperatorsXyz('P 41 21 2', ['X,Y,Z', '-X,-Y,1/2+Z', '1/2-Y,1/2+X,1/4+Z', '1/2+Y,1/2-X,3/4+Z', '1/2-X,1/2+Y,1/4-Z', '1/2+X,1/2-Y,3/4-Z', 'Y,X,-Z', '-Y,-X,1/2-Z'])
checkOperatorsXyz('P 3', ['X,Y,Z', '-Y,X-Y,Z', 'Y-X,-X,Z'])
});
})

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -145,6 +145,54 @@ namespace Spacegroup {
const r3 = TransformData[ids[2]];
return Mat4.ofRows([r1, r2, r3, [0, 0, 0, 1]]);
}
export function getOperatorXyz(op: Mat4) {
return [
formatElement(getRotation(op[0], op[4], op[8]), getShift(op[12])),
formatElement(getRotation(op[1], op[5], op[9]), getShift(op[13])),
formatElement(getRotation(op[2], op[6], op[10]), getShift(op[14]))
].join(',')
}
function getRotation(x: number, y: number, z: number) {
let r: string[] = []
if (x > 0) r.push('+X')
else if (x < 0) r.push('-X')
if (y > 0) r.push('+Y')
else if (y < 0) r.push('-Y')
if (z > 0) r.push('+Z')
else if (z < 0) r.push('-Z')
if (r.length === 1) {
return r[0].charAt(0) === '+' ? r[0].substr(1) : r[0]
}
if (r.length === 2) {
const s0 = r[0].charAt(0)
const s1 = r[1].charAt(0)
if (s0 === '+') return `${r[0].substr(1)}${r[1]}`
if (s1 === '+') return `${r[1].substr(1)}${r[0]}`
}
throw new Error(`unknown rotation '${r}', ${x} ${y} ${z}`)
}
function getShift(s: number) {
switch (s) {
case 1/2: return '1/2'
case 1/4: return '1/4'
case 3/4: return '3/4'
case 1/3: return '1/3'
case 2/3: return '2/3'
case 1/6: return '1/6'
case 5/6: return '5/6'
}
return ''
}
function formatElement(rotation: string, shift: string) {
if (shift === '') return rotation
if (rotation.length > 2) return `${rotation}+${shift}`
return rotation.charAt(0) === '-' ? `${shift}${rotation}` : `${shift}+${rotation}`
}
}
export { Spacegroup, SpacegroupCell }

View File

@@ -90,9 +90,9 @@ function getConformation(atom_site: AtomSite): AtomicConformation {
function isHierarchyDataEqual(a: AtomicData, b: AtomicData) {
// TODO need to cast because of how TS handles type resolution for interfaces https://github.com/Microsoft/TypeScript/issues/15300
return Table.areEqual(a.chains as Table<ChainsSchema>, b.chains as Table<ChainsSchema>)
&& Table.areEqual(a.residues as Table<ResiduesSchema>, b.residues as Table<ResiduesSchema>)
&& Table.areEqual(a.atoms as Table<AtomsSchema>, b.atoms as Table<AtomsSchema>)
return Table.areEqual(a.chains, b.chains)
&& Table.areEqual(a.residues, b.residues)
&& Table.areEqual(a.atoms, b.atoms)
}
function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], previous?: Model) {

View File

@@ -37,6 +37,7 @@ export namespace AssemblySymmetry {
const mmcif = structure.models[0].sourceData.data.db
if (!mmcif.pdbx_struct_assembly.details.isDefined) return false
const id = structure.units[0].conformation.operator.assembly.id
if (id === '' || id === 'deposited') return true
const indices = Column.indicesOf(mmcif.pdbx_struct_assembly.id, e => e === id)
if (indices.length !== 1) return false
const details = mmcif.pdbx_struct_assembly.details.value(indices[0])
@@ -48,7 +49,7 @@ export namespace AssemblySymmetry {
const client = new GraphQLClient(props.serverUrl, ctx.fetch)
const variables: AssemblySymmetryQueryVariables = {
assembly_id: structure.units[0].conformation.operator.assembly.id,
assembly_id: structure.units[0].conformation.operator.assembly.id || 'deposited',
entry_id: structure.units[0].model.entryId
}
const result = await client.request<AssemblySymmetryQuery>(ctx.runtime, query, variables)
@@ -56,7 +57,8 @@ export namespace AssemblySymmetry {
if (!result.assembly?.rcsb_struct_symmetry) {
throw new Error('missing fields')
}
return result.assembly.rcsb_struct_symmetry as AssemblySymmetryValue
const symmetry = result.assembly.rcsb_struct_symmetry as AssemblySymmetryValue
return symmetry.filter(s => s.symbol !== 'C1')
}
}

View File

@@ -1,7 +1,7 @@
/* eslint-disable */
export type Maybe<T> = T | null;
// Generated in 2020-02-07T10:59:45-08:00
// Generated in 2020-02-21T15:58:06-08:00
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
@@ -14,7 +14,6 @@ export type Scalars = {
UNREPRESENTABLE: any,
};
export type AuditAuthor = {
readonly identifier_ORCID?: Maybe<Scalars['String']>,
readonly name?: Maybe<Scalars['String']>,
@@ -1750,6 +1749,7 @@ export type RcsbEntryContainerIdentifiers = {
readonly emdb_ids?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>,
readonly entity_ids?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>,
readonly entry_id: Scalars['String'],
readonly model_ids?: Maybe<ReadonlyArray<Maybe<Scalars['Int']>>>,
readonly non_polymer_entity_ids?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>,
readonly polymer_entity_ids?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>,
readonly pubmed_id?: Maybe<Scalars['Int']>,
@@ -1921,7 +1921,6 @@ export type RcsbNonpolymerInstanceAnnotationAnnotationLineage = {
export type RcsbNonpolymerInstanceFeature = {
readonly assignment_version?: Maybe<Scalars['String']>,
readonly auth_seq_id?: Maybe<Scalars['String']>,
readonly comp_id?: Maybe<Scalars['String']>,
readonly description?: Maybe<Scalars['String']>,
readonly feature_id?: Maybe<Scalars['String']>,
@@ -1942,6 +1941,7 @@ export type RcsbNonpolymerInstanceFeatureFeatureValue = {
};
export type RcsbNonpolymerInstanceFeatureSummary = {
readonly comp_id?: Maybe<Scalars['String']>,
readonly count?: Maybe<Scalars['Int']>,
readonly maximum_length?: Maybe<Scalars['Int']>,
readonly maximum_value?: Maybe<Scalars['Float']>,

View File

@@ -50,7 +50,7 @@ export const AtomsSchema = {
};
export type AtomsSchema = typeof AtomsSchema
export interface Atoms extends Table<AtomsSchema> { }
export type Atoms = Table<AtomsSchema>
export const ResiduesSchema = {
/**
@@ -83,7 +83,7 @@ export const ResiduesSchema = {
pdbx_PDB_ins_code: mmCIF.atom_site.pdbx_PDB_ins_code,
};
export type ResiduesSchema = typeof ResiduesSchema
export interface Residues extends Table<ResiduesSchema> { }
export type Residues = Table<ResiduesSchema>
export const ChainsSchema = {
/**
@@ -102,7 +102,7 @@ export const ChainsSchema = {
label_entity_id: mmCIF.atom_site.label_entity_id
}
export type ChainsSchema = typeof ChainsSchema
export interface Chains extends Table<ChainsSchema> { }
export type Chains = Table<ChainsSchema>
export interface AtomicData {
atoms: Atoms,

View File

@@ -221,7 +221,6 @@ export const WaterNames = new Set([
export const AminoAcidNamesL = new Set([
'HIS', 'ARG', 'LYS', 'ILE', 'PHE', 'LEU', 'TRP', 'ALA', 'MET', 'PRO', 'CYS',
'ASN', 'VAL', 'GLY', 'SER', 'GLN', 'TYR', 'ASP', 'GLU', 'THR', 'SEC', 'PYL',
'UNK' // unknown amino acid from CCD
])
export const AminoAcidNamesD = new Set([
@@ -249,8 +248,14 @@ export const AminoAcidNamesD = new Set([
])
export const AminoAcidNames = SetUtils.unionMany(AminoAcidNamesL, AminoAcidNamesD)
export const RnaBaseNames = new Set([ 'A', 'C', 'T', 'G', 'I', 'U' ])
export const DnaBaseNames = new Set([ 'DA', 'DC', 'DT', 'DG', 'DI', 'DU' ])
export const RnaBaseNames = new Set([
'A', 'C', 'T', 'G', 'I', 'U',
'N' // unknown RNA base from CCD
])
export const DnaBaseNames = new Set([
'DA', 'DC', 'DT', 'DG', 'DI', 'DU',
'DN' // unknown DNA base from CCD
])
export const PeptideBaseNames = new Set([ 'APN', 'CPN', 'TPN', 'GPN' ])
export const PurineBaseNames = new Set([ 'A', 'G', 'DA', 'DG', 'DI', 'APN', 'GPN' ])
export const PyrimidineBaseNames = new Set([ 'C', 'T', 'U', 'DC', 'DT', 'DU', 'CPN', 'TPN' ])

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -11,10 +11,11 @@ import { Spacegroup, SpacegroupCell, SymmetryOperator } from '../../../mol-math/
import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
import { RuntimeContext, Task } from '../../../mol-task';
import { Symmetry, Model } from '../model';
import { QueryContext, StructureSelection } from '../query';
import { QueryContext, StructureSelection, Queries as Q } from '../query';
import Structure from './structure';
import Unit from './unit';
import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
import StructureProperties from './properties';
namespace StructureSymmetry {
export function buildAssembly(structure: Structure, asmName: string) {
@@ -48,6 +49,40 @@ namespace StructureSymmetry {
});
}
export type Generators = { operators: { index: number, shift: Vec3 }[], asymIds: string[] }[]
export function buildSymmetryAssembly(structure: Structure, generators: Generators, symmetry: Symmetry) {
return Task.create('Build Symmetry Assembly', async ctx => {
const models = structure.models;
if (models.length !== 1) throw new Error('Can only build symmetry assemblies from structures based on 1 model.');
const modelCenter = Vec3() // Model.getCenter(models[0])
const assembler = Structure.Builder({ label: structure.label });
const queryCtx = new QueryContext(structure);
for (const g of generators) {
const selector = getSelector(g.asymIds);
const selection = selector(queryCtx);
if (StructureSelection.structureCount(selection) === 0) {
continue;
}
const { units } = StructureSelection.unionStructure(selection);
for (const { index, shift: [i, j, k] } of g.operators) {
const operators = getOperatorsForIndex(symmetry, index, i, j, k, modelCenter)
for (const unit of units) {
for (const op of operators) {
assembler.addWithOperator(unit, op);
}
}
}
}
return assembler.getStructure();
});
}
export function builderSymmetryMates(structure: Structure, radius: number) {
return Task.create('Find Symmetry Mates', ctx => findMatesRadius(ctx, structure, radius));
}
@@ -96,7 +131,35 @@ namespace StructureSymmetry {
}
}
function getOperators(symmetry: Symmetry, ijkMin: Vec3, ijkMax: Vec3, modelCenter: Vec3) {
function getSelector(asymIds: string[]) {
return Q.generators.atoms({ chainTest: Q.pred.and(
Q.pred.eq(ctx => StructureProperties.unit.operator_name(ctx.element), SymmetryOperator.DefaultName),
Q.pred.inSet(ctx => StructureProperties.chain.label_asym_id(ctx.element), asymIds)
)});
}
function getOperatorsForIndex(symmetry: Symmetry, index: number, i: number, j: number, k: number, modelCenter: Vec3) {
const { spacegroup, ncsOperators } = symmetry;
const operators: SymmetryOperator[] = []
const { toFractional } = spacegroup.cell
const ref = Vec3.transformMat4(Vec3(), modelCenter, toFractional)
const symOp = Spacegroup.getSymmetryOperatorRef(spacegroup, index, i, j, k, ref)
if (ncsOperators && ncsOperators.length) {
for (let u = 0, ul = ncsOperators.length; u < ul; ++u) {
const ncsOp = ncsOperators![u]
const matrix = Mat4.mul(Mat4(), symOp.matrix, ncsOp.matrix)
const operator = SymmetryOperator.create(`${symOp.name} ${ncsOp.name}`, matrix, symOp.assembly, ncsOp.ncsId, symOp.hkl, symOp.spgrOp);
operators.push(operator)
}
} else {
operators.push(symOp)
}
return operators
}
function getOperatorsForRange(symmetry: Symmetry, ijkMin: Vec3, ijkMax: Vec3, modelCenter: Vec3) {
const { spacegroup, ncsOperators } = symmetry;
const ncsCount = (ncsOperators && ncsOperators.length) || 0
const operators: SymmetryOperator[] = [];
@@ -117,18 +180,7 @@ function getOperators(symmetry: Symmetry, ijkMin: Vec3, ijkMax: Vec3, modelCente
for (let k = ijkMin[2]; k <= ijkMax[2]; k++) {
// check if we have added identity as the 1st operator.
if (!ncsCount && op === 0 && i === 0 && j === 0 && k === 0) continue;
const symOp = Spacegroup.getSymmetryOperatorRef(spacegroup, op, i, j, k, ref)
if (ncsCount) {
for (let u = 0; u < ncsCount; ++u) {
const ncsOp = ncsOperators![u]
const matrix = Mat4.mul(Mat4.zero(), symOp.matrix, ncsOp.matrix)
const operator = SymmetryOperator.create(`${symOp.name} ${ncsOp.name}`, matrix, symOp.assembly, ncsOp.ncsId, symOp.hkl, symOp.spgrOp);
operators[operators.length] = operator;
}
} else {
operators[operators.length] = symOp;
}
operators.push(...getOperatorsForIndex(symmetry, op, i, j, k, ref))
}
}
}
@@ -142,7 +194,7 @@ function getOperatorsCached333(symmetry: Symmetry, ref: Vec3) {
}
symmetry._operators_333 = {
ref: Vec3.clone(ref),
operators: getOperators(symmetry, Vec3.create(-3, -3, -3), Vec3.create(3, 3, 3), ref)
operators: getOperatorsForRange(symmetry, Vec3.create(-3, -3, -3), Vec3.create(3, 3, 3), ref)
};
return symmetry._operators_333.operators;
}
@@ -181,7 +233,7 @@ async function findSymmetryRange(ctx: RuntimeContext, structure: Structure, ijkM
if (SpacegroupCell.isZero(spacegroup.cell)) return structure;
const modelCenter = Model.getCenter(models[0])
const operators = getOperators(symmetry, ijkMin, ijkMax, modelCenter);
const operators = getOperatorsForRange(symmetry, ijkMin, ijkMax, modelCenter);
return assembleOperators(structure, operators);
}

View File

@@ -21,7 +21,7 @@ const SimpleSettingsParams = {
'transparent': PD.EmptyGroup(),
'opaque': PD.Group({ color: PD.Color(Color(0xFCFBF9), { description: 'Custom background color' }) }, { isFlat: true })
}, { description: 'Background of the 3D canvas' }),
renderStyle: PD.Select('glossy', [['toon', 'Toon'], ['matte', 'Matte'], ['glossy', 'Glossy'], ['metallic', 'Metallic']], { description: 'Style in which the 3D scene is rendered' }),
renderStyle: PD.Select('glossy', [['flat', 'Flat'], ['matte', 'Matte'], ['glossy', 'Glossy'], ['metallic', 'Metallic']], { description: 'Style in which the 3D scene is rendered' }),
occlusion: PD.Boolean(false, { description: 'Darken occluded crevices with the ambient occlusion effect' }),
outline: PD.Boolean(false, { description: 'Draw outline around 3D objects' }),
fog: PD.Boolean(false, { description: 'Show fog in the distance' }),
@@ -49,7 +49,7 @@ export class SimpleSettingsControl extends PluginUIComponent {
if (!this.plugin.canvas3d) return;
const renderer = this.plugin.canvas3d.props.renderer;
if (p.value === 'toon') {
if (p.value === 'flat') {
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
renderer: { ...renderer, lightIntensity: 0, ambientIntensity: 1, roughness: 0.4, metalness: 0 }
} });
@@ -80,7 +80,7 @@ export class SimpleSettingsControl extends PluginUIComponent {
} });
} else if (p.name === 'fog') {;
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
cameraFog: p.value ? 50 : 1,
cameraFog: p.value ? 50 : 0,
} });
} else if (p.name === 'clipFar') {;
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
@@ -97,7 +97,7 @@ export class SimpleSettingsControl extends PluginUIComponent {
if (renderer) {
if (renderer.lightIntensity === 0 && renderer.ambientIntensity === 1 && renderer.roughness === 0.4 && renderer.metalness === 0) {
renderStyle = 'toon'
renderStyle = 'flat'
} else if (renderer.lightIntensity === 0.6 && renderer.ambientIntensity === 0.4) {
if (renderer.roughness === 1 && renderer.metalness === 0) {
renderStyle = 'matte'

View File

@@ -61,6 +61,8 @@ const InitAssemblySymmetry3D = StateAction.build({
await state.updateTree(tree).runInContext(ctx);
}));
export { AssemblySymmetry3D }
type AssemblySymmetry3D = typeof AssemblySymmetry3D
const AssemblySymmetry3D = PluginStateTransform.BuiltIn({
name: 'rcsb-assembly-symmetry-3d',

View File

@@ -1,12 +1,13 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Model, Structure, StructureSymmetry } from '../../../mol-model/structure';
import { stringToWords } from '../../../mol-util/string';
import { SpacegroupCell } from '../../../mol-math/geometry';
import { SpacegroupCell, Spacegroup } from '../../../mol-math/geometry';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { RuntimeContext } from '../../../mol-task';
@@ -14,14 +15,33 @@ import { PluginContext } from '../../context';
import { Assembly, Symmetry } from '../../../mol-model/structure/model/properties/symmetry';
import { PluginStateObject as SO } from '../objects';
import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
export namespace ModelStructureRepresentation {
export function getParams(model?: Model, defaultValue?: 'deposited' | 'assembly' | 'symmetry' | 'symmetry-mates') {
export function getParams(model?: Model, defaultValue?: 'deposited' | 'assembly' | 'symmetry' | 'symmetry-mates' | 'symmetry-assembly') {
const symmetry = model && ModelSymmetry.Provider.get(model)
const assemblyIds = symmetry ? symmetry.assemblies.map(a => [a.id, `${a.id}: ${stringToWords(a.details)}`] as [string, string]) : [];
const showSymm = !symmetry ? true : !SpacegroupCell.isZero(symmetry.spacegroup.cell);
const operatorOptions: [number, string][] = []
if (symmetry) {
const { operators } = symmetry.spacegroup
for (let i = 0, il = operators.length; i < il; i++) {
operatorOptions.push([i, `${i + 1}: ${Spacegroup.getOperatorXyz(operators[i])}`])
}
}
const asymIdsOptions: [string, string][] = []
if (model && MmcifFormat.is(model?.sourceData)) {
// TODO make generally available for models, also include auth_asym_id
const { struct_asym } = model.sourceData.data.db
for (let i = 0, il = struct_asym._rowCount; i < il; ++i) {
const id = struct_asym.id.value(i)
asymIdsOptions.push([id, id])
}
}
const modes = {
deposited: PD.EmptyGroup(),
assembly: PD.Group({
@@ -35,6 +55,19 @@ export namespace ModelStructureRepresentation {
'symmetry': PD.Group({
ijkMin: PD.Vec3(Vec3.create(-1, -1, -1), { label: 'Min IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } }),
ijkMax: PD.Vec3(Vec3.create(1, 1, 1), { label: 'Max IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } })
}, { isFlat: true }),
'symmetry-assembly': PD.Group({
generators: PD.ObjectList({
operators: PD.ObjectList({
index: PD.Select(0, operatorOptions),
shift: PD.Vec3(Vec3(), { label: 'IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } })
}, e => `${e.index + 1}_${e.shift.map(a => a + 5).join('')}`, {
defaultValue: [] as { index: number, shift: Vec3 }[]
}),
asymIds: PD.MultiSelect([] as string[], asymIdsOptions)
}, e => `${e.asymIds.length} asym ids, ${e.operators.length} operators`, {
defaultValue: [] as { operators: { index: number, shift: Vec3 }[], asymIds: string[] }[]
})
}, { isFlat: true })
};
@@ -49,6 +82,7 @@ export namespace ModelStructureRepresentation {
if (showSymm) {
options.push(['symmetry-mates', 'Symmetry Mates']);
options.push(['symmetry', 'Symmetry (indices)']);
options.push(['symmetry-assembly', 'Symmetry (assembly)']);
}
return {
@@ -105,8 +139,16 @@ export namespace ModelStructureRepresentation {
return new SO.Molecule.Structure(s, props);
}
async function buildSymmetryAssembly(ctx: RuntimeContext, model: Model, generators: StructureSymmetry.Generators, symmetry: Symmetry) {
const base = Structure.ofModel(model);
const s = await StructureSymmetry.buildSymmetryAssembly(base, generators, symmetry).runInContext(ctx);
const props = { label: `Symmetry Assembly`, description: Structure.elementDescription(s) };
return new SO.Molecule.Structure(s, props);
}
export async function create(plugin: PluginContext, ctx: RuntimeContext, model: Model, params?: Params): Promise<SO.Molecule.Structure> {
if (!params || params.name === 'deposited') {
const symmetry = ModelSymmetry.Provider.get(model)
if (!symmetry || !params || params.name === 'deposited') {
const s = Structure.ofModel(model);
return new SO.Molecule.Structure(s, { label: 'Deposited', description: Structure.elementDescription(s) });
}
@@ -119,6 +161,9 @@ export namespace ModelStructureRepresentation {
if (params.name === 'symmetry-mates') {
return buildSymmetryMates(ctx, model, params.params.radius)
}
if (params.name === 'symmetry-assembly') {
return buildSymmetryAssembly(ctx, model, params.params.generators, symmetry)
}
throw new Error(`Unknown represetation type: ${(params as any).name}`);
}