Compare commits

...

17 Commits

Author SHA1 Message Date
Alexander Rose
f0b54e9cbf 0.2.8 2019-09-04 12:18:43 -07:00
Alexander Rose
fdb45c3624 improved canvas3d.clear 2019-09-04 12:17:22 -07:00
Alexander Rose
f3b1ec0ed8 Merge branch 'master' of https://github.com/molstar/molstar 2019-09-04 12:05:42 -07:00
Alexander Rose
79510614b9 add .uniqueElementCount and .polymerUnitCount to Structure 2019-09-04 12:04:58 -07:00
Alexander Rose
f8c9bc6812 add volume-server npm start script 2019-09-04 12:03:44 -07:00
Alexander Rose
85d35aab90 handle special CCP4 spacegroup numbers 2019-09-04 12:03:14 -07:00
David Sehnal
f5b09dbd10 mol-plugin: added customState to PluginContext 2019-09-03 14:42:59 +02:00
Alexander Rose
57ea322fd6 cellpack loader tweak 2019-08-30 19:09:53 -07:00
Alexander Rose
c31aab5594 wip, cellpack loader 2019-08-30 18:56:30 -07:00
Alexander Rose
a5556e8c41 imroved sequence widget entity dropdown 2019-08-30 18:55:46 -07:00
Alexander Rose
0ce2966c47 lazy calculation of Structure.polymerResidueCount 2019-08-30 18:54:54 -07:00
Alexander Rose
f2c04a13af adjust grid-cell-count in partitionAtomicUnitByResidue 2019-08-30 17:28:33 -07:00
Alexander Rose
60ba0de219 CifField creation optimizations 2019-08-30 17:27:58 -07:00
Alexander Rose
99e515604e skip identical positions in spheres and lines bounding sphere calculation 2019-08-30 17:26:52 -07:00
Alexander Rose
84dfa60fc2 added CifCategory.ofTable and 'list' valueType support for CifField.ofColumn 2019-08-29 17:42:30 -07:00
Alexander Rose
a753095f92 added ImportString and ImportJson transforms 2019-08-29 17:31:44 -07:00
David Sehnal
ce0ff2fed8 mol-model: optionally compute centroid based bounding sphere (disabled for now) 2019-08-29 15:46:47 +02:00
17 changed files with 449 additions and 78 deletions

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "0.2.7",
"version": "0.2.8",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "0.2.7",
"version": "0.2.8",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -24,6 +24,7 @@
"serve": "http-server -p 1338",
"model-server": "node lib/servers/model/server.js",
"model-server-watch": "nodemon --watch lib lib/servers/model/server.js",
"volume-server": "node lib/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
"preversion": "npm run test",
"postversion": "git push && git push --tags",
"prepublishOnly": "npm run test && npm run build"

View File

@@ -8,7 +8,7 @@ import { StateAction } 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, CellPacking } from './data';
import { Ingredient, CellPacking, Cell } from './data';
import { getFromPdb, getFromCellPackDB } from './util';
import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext } from '../../../../mol-model/structure';
import { trajectoryFromMmCIF } from '../../../../mol-model-formats/structure/mmcif';
@@ -28,6 +28,11 @@ import { compile } from '../../../../mol-script/runtime/query/compiler';
import { UniformColorThemeProvider } from '../../../../mol-theme/color/uniform';
import { ThemeRegistryContext } from '../../../../mol-theme/theme';
import { ColorTheme } from '../../../../mol-theme/color';
import { _parse_mmCif } from '../../../../mol-model-formats/structure/mmcif/parser';
import { ModelFormat } from '../../../../mol-model-formats/structure/format';
import { CifCategory, CifField } from '../../../../mol-io/reader/cif';
import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
import { Column } from '../../../../mol-data/db';
function getCellPackModelUrl(fileName: string, baseUrl: string) {
return `${baseUrl}/results/${fileName}`
@@ -118,6 +123,109 @@ function getAssembly(transforms: Mat4[], structure: Structure) {
return builder.getStructure();
}
function getCifCurve(name: string, transforms: Mat4[], model: Model) {
const d = model.sourceData.data.atom_site
const n = d._rowCount
const rowCount = n * transforms.length
const { offsets, count } = model.atomicHierarchy.chainAtomSegments
const x = d.Cartn_x.toArray()
const y = d.Cartn_y.toArray()
const z = d.Cartn_z.toArray()
const Cartn_x = new Float32Array(rowCount)
const Cartn_y = new Float32Array(rowCount)
const Cartn_z = new Float32Array(rowCount)
const map = new Uint32Array(rowCount)
const seq = new Int32Array(rowCount)
let offset = 0
for (let c = 0; c < count; ++c) {
const cStart = offsets[c]
const cEnd = offsets[c + 1]
const cLength = cEnd - cStart
for (let t = 0, tl = transforms.length; t < tl; ++t) {
const m = transforms[t]
for (let j = cStart; j < cEnd; ++j) {
const i = offset + j - cStart
const xj = x[j], yj = y[j], zj = z[j]
Cartn_x[i] = m[0] * xj + m[4] * yj + m[8] * zj + m[12]
Cartn_y[i] = m[1] * xj + m[5] * yj + m[9] * zj + m[13]
Cartn_z[i] = m[2] * xj + m[6] * yj + m[10] * zj + m[14]
map[i] = j
seq[i] = t + 1
}
offset += cLength
}
}
function multColumn<T>(column: Column<T>) {
const array = column.toArray()
return Column.ofLambda({
value: row => array[map[row]],
areValuesEqual: (rowA, rowB) => map[rowA] === map[rowB] || array[map[rowA]] === array[map[rowB]],
rowCount, schema: column.schema
})
}
const _atom_site: CifCategory.SomeFields<mmCIF_Schema['atom_site']> = {
auth_asym_id: CifField.ofColumn(multColumn(d.auth_asym_id)),
auth_atom_id: CifField.ofColumn(multColumn(d.auth_atom_id)),
auth_comp_id: CifField.ofColumn(multColumn(d.auth_comp_id)),
auth_seq_id: CifField.ofNumbers(seq),
B_iso_or_equiv: CifField.ofColumn(Column.ofConst(0, rowCount, Column.Schema.float)),
Cartn_x: CifField.ofNumbers(Cartn_x),
Cartn_y: CifField.ofNumbers(Cartn_y),
Cartn_z: CifField.ofNumbers(Cartn_z),
group_PDB: CifField.ofColumn(Column.ofConst('ATOM', rowCount, Column.Schema.str)),
id: CifField.ofColumn(Column.ofLambda({
value: row => row,
areValuesEqual: (rowA, rowB) => rowA === rowB,
rowCount, schema: d.id.schema,
})),
label_alt_id: CifField.ofColumn(multColumn(d.label_alt_id)),
label_asym_id: CifField.ofColumn(multColumn(d.label_asym_id)),
label_atom_id: CifField.ofColumn(multColumn(d.label_atom_id)),
label_comp_id: CifField.ofColumn(multColumn(d.label_comp_id)),
label_seq_id: CifField.ofNumbers(seq),
label_entity_id: CifField.ofColumn(Column.ofConst('1', rowCount, Column.Schema.str)),
occupancy: CifField.ofColumn(Column.ofConst(1, rowCount, Column.Schema.float)),
type_symbol: CifField.ofColumn(multColumn(d.type_symbol)),
pdbx_PDB_ins_code: CifField.ofColumn(Column.ofConst('', rowCount, Column.Schema.str)),
pdbx_PDB_model_num: CifField.ofColumn(Column.ofConst(1, rowCount, Column.Schema.int)),
}
const categories = {
entity: CifCategory.ofTable('entity', model.sourceData.data.entity),
chem_comp: CifCategory.ofTable('chem_comp', model.sourceData.data.chem_comp),
atom_site: CifCategory.ofFields('atom_site', _atom_site)
}
return {
header: name,
categoryNames: Object.keys(categories),
categories
};
}
async function getCurve(name: string, transforms: Mat4[], model: Model) {
const cif = getCifCurve(name, transforms, model)
const curveModelTask = Task.create('Curve Model', async ctx => {
const format = ModelFormat.mmCIF(cif)
const models = await _parse_mmCif(format, ctx)
return models[0]
})
const curveModel = await curveModelTask.run()
return getStructure(curveModel)
}
async function getIngredientStructure(ingredient: Ingredient, baseUrl: string) {
const { name, source, results, nbCurve } = ingredient
@@ -133,10 +241,12 @@ async function getIngredientStructure(ingredient: Ingredient, baseUrl: string) {
const model = await getModel(source.pdb || name, baseUrl)
if (!model) return
const structure = await getStructure(model, { assembly: source.biomt ? '1' : undefined })
const transforms = nbCurve ? getCurveTransforms(ingredient) : getResultTransforms(results)
const assembly = getAssembly(transforms, structure)
return assembly
if (nbCurve) {
return getCurve(name, getCurveTransforms(ingredient), model)
} else {
const structure = await getStructure(model, { assembly: source.biomt ? '1' : undefined })
return getAssembly(getResultTransforms(results), structure)
}
}
export function createStructureFromCellPack(packing: CellPacking, baseUrl: string) {
@@ -144,14 +254,16 @@ export function createStructureFromCellPack(packing: CellPacking, baseUrl: strin
const { ingredients, name } = packing
const structures: Structure[] = []
for (const iName in ingredients) {
if (ctx.shouldUpdate) ctx.update(iName)
if (ctx.shouldUpdate) await ctx.update(iName)
const s = await getIngredientStructure(ingredients[iName], baseUrl)
if (s) structures.push(s)
}
if (ctx.shouldUpdate) await ctx.update(`${name} - units`)
const builder = Structure.Builder({ label: name })
let offsetInvariantId = 0
for (const s of structures) {
if (ctx.shouldUpdate) await ctx.update(`${s.label}`)
let maxInvariantId = 0
for (const u of s.units) {
const invariantId = u.invariantId + offsetInvariantId
@@ -161,6 +273,7 @@ export function createStructureFromCellPack(packing: CellPacking, baseUrl: strin
offsetInvariantId += maxInvariantId
}
if (ctx.shouldUpdate) await ctx.update(`${name} - structure`)
const s = builder.getStructure()
return s
})
@@ -175,6 +288,7 @@ export const LoadCellPackModel = StateAction.build({
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV-1_0.1.6-8_mixed_radii_pdb'],
['influenza_model1.json', 'influenza_model1'],
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
['curveTest', 'Curve Test'],
]),
baseUrl: PD.Text(DefaultCellPackBaseUrl),
preset: PD.Group({
@@ -192,14 +306,50 @@ export const LoadCellPackModel = StateAction.build({
const root = state.build().toRoot();
const cellPackBuilder = root
.apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.id }, { state: { isGhost: true } })
.apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
.apply(ParseCellPack)
let cellPackBuilder: any
if (params.id === 'curveTest') {
const url = `${params.baseUrl}/extras/rna_allpoints.json`
const data = await ctx.fetch({ url, type: 'string' }).runInContext(taskCtx);
const { points } = await (new Response(data)).json() as { points: number[] }
const curve0: Vec3[] = []
for (let j = 0, jl = Math.min(points.length, 3 * 100); j < jl; j += 3) {
curve0.push(Vec3.fromArray(Vec3(), points, j))
}
const cell: Cell = {
recipe: { setupfile: '', paths: [], version: '', name: 'Curve Test' },
compartments: {
'CurveCompartment': {
interior: {
ingredients: {
'CurveIngredient': {
source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
results: [],
name: 'RNA',
nbCurve: 1,
curve0
}
}
}
}
}
}
cellPackBuilder = root
.apply(StateTransforms.Data.ImportJson, { data: cell }, { state: { isGhost: true } })
.apply(ParseCellPack)
} else {
cellPackBuilder = root
.apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.id }, { state: { isGhost: true } })
.apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
.apply(ParseCellPack)
}
const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(taskCtx)
const { packings } = cellPackObject.data
let tree = state.build().to(cellPackBuilder.ref);
const tree = state.build().to(cellPackBuilder.ref);
const isHiv = (
params.id === 'BloodHIV1.0_mixed_fixed_nc1.cpr' ||
@@ -256,7 +406,7 @@ export const LoadCellPackModel = StateAction.build({
if (isHiv) {
const url = `${params.baseUrl}/membranes/hiv_lipids.bcif`
tree.apply(StateTransforms.Data.Download, { url, isBinary: true }, { state: { isGhost: true } })
tree.apply(StateTransforms.Data.Download, { label: 'hiv_lipids', url, isBinary: true }, { state: { isGhost: true } })
.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
@@ -270,7 +420,9 @@ export const LoadCellPackModel = StateAction.build({
)
}
console.time('cellpack')
await state.updateTree(tree).runInContext(taskCtx);
console.timeEnd('cellpack')
}));
function getReprParams(ctx: PluginContext, params: { representation: 'spacefill' | 'gaussian-surface' | 'point', traceOnly: boolean }) {

View File

@@ -14,8 +14,8 @@ async function parseCif(data: string|Uint8Array) {
return parsed.result;
}
async function parsePDBfile(data: string) {
const comp = parsePDB(data);
async function parsePDBfile(data: string, id: string) {
const comp = parsePDB(data, id);
const parsed = await comp.run();
if (parsed.isError) throw parsed;
return parsed.result;
@@ -26,9 +26,9 @@ async function downloadCif(url: string, isBinary: boolean) {
return parseCif(isBinary ? new Uint8Array(await data.arrayBuffer()) : await data.text());
}
async function downloadPDB(url: string) {
async function downloadPDB(url: string, id: string) {
const data = await fetch(url);
return parsePDBfile(await data.text());
return parsePDBfile(await data.text(), id);
}
export async function getFromPdb(id: string) {
@@ -42,6 +42,7 @@ function getCellPackDataUrl(id: string, baseUrl: string) {
}
export async function getFromCellPackDB(id: string, baseUrl: string) {
const parsed = await downloadPDB(getCellPackDataUrl(id, baseUrl));
const name = id.endsWith('.pdb') ? id.substring(0, id.length - 4) : id
const parsed = await downloadPDB(getCellPackDataUrl(id, baseUrl), name);
return parsed;
}

View File

@@ -333,6 +333,8 @@ namespace Canvas3D {
reprRenderObjects.clear()
scene.clear()
debugHelper.clear()
requestDraw(true)
reprCount.next(reprRenderObjects.size)
},
// draw,

View File

@@ -73,6 +73,7 @@ namespace Column {
rowCount: number,
schema: T,
valueKind?: (row: number) => ValueKind,
areValuesEqual?: (rowA: number, rowB: number) => boolean
}
export interface ArraySpec<T extends Schema> {
@@ -247,7 +248,7 @@ function constColumn<T extends Column.Schema>(v: T['T'], rowCount: number, schem
}
}
function lambdaColumn<T extends Column.Schema>({ value, valueKind, rowCount, schema }: Column.LambdaSpec<T>): Column<T['T']> {
function lambdaColumn<T extends Column.Schema>({ value, valueKind, areValuesEqual, rowCount, schema }: Column.LambdaSpec<T>): Column<T['T']> {
return {
schema: schema,
__array: void 0,
@@ -260,7 +261,7 @@ function lambdaColumn<T extends Column.Schema>({ value, valueKind, rowCount, sch
for (let i = 0, _i = array.length; i < _i; i++) array[i] = value(i + start);
return array;
},
areValuesEqual: (rowA, rowB) => value(rowA) === value(rowB)
areValuesEqual: areValuesEqual ? areValuesEqual : (rowA, rowB) => value(rowA) === value(rowB)
}
}

View File

@@ -177,8 +177,8 @@ export namespace Lines {
}
function getBoundingSphere(lineStart: Float32Array, lineEnd: Float32Array, lineCount: number, transform: Float32Array, transformCount: number) {
const start = calculateBoundingSphere(lineStart, lineCount * 4, transform, transformCount)
const end = calculateBoundingSphere(lineEnd, lineCount * 4, transform, transformCount)
const start = calculateBoundingSphere(lineStart, lineCount * 4, transform, transformCount, 0, 4)
const end = calculateBoundingSphere(lineEnd, lineCount * 4, transform, transformCount, 0, 4)
return {
boundingSphere: Sphere3D.expandBySphere(start.boundingSphere, end.boundingSphere),
invariantBoundingSphere: Sphere3D.expandBySphere(start.invariantBoundingSphere, end.invariantBoundingSphere)

View File

@@ -89,7 +89,7 @@ export namespace Spheres {
const padding = getMaxSize(size)
const { boundingSphere, invariantBoundingSphere } = calculateBoundingSphere(
spheres.centerBuffer.ref.value, spheres.sphereCount * 4,
transform.aTransform.ref.value, instanceCount, padding
transform.aTransform.ref.value, instanceCount, padding, 4
)
return {
@@ -130,7 +130,7 @@ export namespace Spheres {
const padding = getMaxSize(values)
const { boundingSphere, invariantBoundingSphere } = calculateBoundingSphere(
values.aPosition.ref.value, spheres.sphereCount * 4,
values.aTransform.ref.value, values.instanceCount.ref.value, padding
values.aTransform.ref.value, values.instanceCount.ref.value, padding, 4
)
if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
ValueCell.update(values.boundingSphere, boundingSphere)

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -80,14 +80,15 @@ export function printImageData(imageData: ImageData, scale = 1, pixelated = fals
const v = Vec3.zero()
const boundaryHelper = new BoundaryHelper()
export function calculateInvariantBoundingSphere(position: Float32Array, positionCount: number): Sphere3D {
export function calculateInvariantBoundingSphere(position: Float32Array, positionCount: number, stepFactor: number): Sphere3D {
const step = stepFactor * 3
boundaryHelper.reset(0)
for (let i = 0, _i = positionCount * 3; i < _i; i += 3) {
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
Vec3.fromArray(v, position, i)
boundaryHelper.boundaryStep(v, 0)
}
boundaryHelper.finishBoundaryStep()
for (let i = 0, _i = positionCount * 3; i < _i; i += 3) {
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
Vec3.fromArray(v, position, i)
boundaryHelper.extendStep(v, 0)
}
@@ -109,8 +110,8 @@ export function calculateTransformBoundingSphere(invariantBoundingSphere: Sphere
return boundaryHelper.getSphere()
}
export function calculateBoundingSphere(position: Float32Array, positionCount: number, transform: Float32Array, transformCount: number, padding = 0): { boundingSphere: Sphere3D, invariantBoundingSphere: Sphere3D } {
const invariantBoundingSphere = calculateInvariantBoundingSphere(position, positionCount)
export function calculateBoundingSphere(position: Float32Array, positionCount: number, transform: Float32Array, transformCount: number, padding = 0, stepFactor = 1): { boundingSphere: Sphere3D, invariantBoundingSphere: Sphere3D } {
const invariantBoundingSphere = calculateInvariantBoundingSphere(position, positionCount, stepFactor)
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform, transformCount)
Sphere3D.expand(boundingSphere, boundingSphere, padding)
Sphere3D.expand(invariantBoundingSphere, invariantBoundingSphere, padding)

View File

@@ -5,7 +5,7 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Column, ColumnHelpers } from '../../../mol-data/db'
import { Column, ColumnHelpers, Table } from '../../../mol-data/db'
import { Tensor } from '../../../mol-math/linear-algebra'
import { getNumberType, NumberType, parseInt as fastParseInt, parseFloat as fastParseFloat } from '../common/text/number-parser';
import { Encoding } from '../../common/binary-cif';
@@ -70,6 +70,14 @@ export namespace CifCategory {
getField(name) { return fields[name]; }
};
}
export function ofTable(name: string, table: Table<any>) {
const fields: { [name: string]: CifField | undefined } = {}
for (const name of table._columns) {
fields[name] = CifField.ofColumn(table[name])
}
return ofFields(name, fields)
}
}
/**
@@ -126,7 +134,7 @@ export namespace CifField {
float,
valueKind,
areValuesEqual: (rowA, rowB) => values[rowA] === values[rowB],
toStringArray: params => ColumnHelpers.createAndFillArray(rowCount, str, params),
toStringArray: params => params ? ColumnHelpers.createAndFillArray(rowCount, str, params) : values as string[],
toIntArray: params => ColumnHelpers.createAndFillArray(rowCount, int, params),
toFloatArray: params => ColumnHelpers.createAndFillArray(rowCount, float, params)
}
@@ -138,6 +146,14 @@ export namespace CifField {
const float: CifField['float'] = row => values[row];
const valueKind: CifField['valueKind'] = row => Column.ValueKind.Present;
const toFloatArray = (params: Column.ToArrayParams<number>) => {
if (!params || params.array && values instanceof params.array) {
return values as number[]
} else {
return ColumnHelpers.createAndFillArray(rowCount, float, params)
}
}
return {
__array: void 0,
binaryEncoding: void 0,
@@ -149,8 +165,8 @@ export namespace CifField {
valueKind,
areValuesEqual: (rowA, rowB) => values[rowA] === values[rowB],
toStringArray: params => ColumnHelpers.createAndFillArray(rowCount, str, params),
toIntArray: params => ColumnHelpers.createAndFillArray(rowCount, float, params),
toFloatArray: params => ColumnHelpers.createAndFillArray(rowCount, float, params)
toIntArray: toFloatArray,
toFloatArray
}
}
@@ -208,16 +224,22 @@ export namespace CifField {
case 'float':
case 'int':
str = row => { return '' + column.value(row); };
int = row => column.value(row);
float = row => column.value(row);
int = column.value;
float = column.value;
break
case 'str':
str = row => column.value(row);
str = column.value;
int = row => { const v = column.value(row); return fastParseInt(v, 0, v.length) || 0; };
float = row => { const v = column.value(row); return fastParseFloat(v, 0, v.length) || 0; };
break
case 'list':
const { separator } = column.schema;
str = row => column.value(row).join(separator);
int = row => NaN;
float = row => NaN;
break
default:
throw new Error('unsupported')
throw new Error(`unsupported valueType '${column.schema.valueType}'`)
}
return {

View File

@@ -5,6 +5,7 @@
*/
import { Vec3 } from '../../mol-math/linear-algebra/3d';
import { Sphere3D } from './primitives/sphere3d';
export { CentroidHelper }
@@ -35,6 +36,10 @@ class CentroidHelper {
if (d > this.radiusSq) this.radiusSq = d;
}
getSphere(): Sphere3D {
return { center: Vec3.clone(this.center), radius: Math.sqrt(this.radiusSq) };
}
constructor() {
}

View File

@@ -1345,7 +1345,17 @@ export const SpacegroupNames: { [num: number]: SpacegroupName } = (function () {
// return -1 if the spacegroup does not exist.
export function getSpacegroupIndex(nameOrNumber: number | string | SpacegroupName): number {
const index = typeof nameOrNumber === 'number' ? nameOrNumber - 1 : ZeroBasedSpacegroupNumbers[nameOrNumber as SpacegroupName];
let index: number
if (typeof nameOrNumber === 'number') {
// used by CCP4, see http://www.ccp4.ac.uk/html/pointless.html#setting
if (nameOrNumber === 1017) index = 254
else if (nameOrNumber === 2017) index = 255
else if (nameOrNumber === 2018) index = 257
else if (nameOrNumber === 3018) index = 258
else index = nameOrNumber - 1
} else {
index = ZeroBasedSpacegroupNumbers[nameOrNumber as SpacegroupName];
}
if (typeof index === 'undefined' || typeof SpacegroupNames[index] === 'undefined') return -1;
return index;
}

View File

@@ -56,7 +56,9 @@ class Structure {
/** Hash based on all unit.id values in the structure, reflecting the units transformation */
transformHash: number,
elementCount: number,
uniqueElementCount: number,
polymerResidueCount: number,
polymerUnitCount: number,
coordinateSystem: SymmetryOperator,
label: string,
propertyData?: any,
@@ -64,8 +66,10 @@ class Structure {
} = {
hashCode: -1,
transformHash: -1,
elementCount: 0,
polymerResidueCount: 0,
elementCount: -1,
uniqueElementCount: -1,
polymerResidueCount: -1,
polymerUnitCount: -1,
coordinateSystem: SymmetryOperator.Default,
label: ''
};
@@ -105,9 +109,26 @@ class Structure {
/** Count of all polymer residues in the structure */
get polymerResidueCount() {
if (this._props.polymerResidueCount === -1) {
this._props.polymerResidueCount = getPolymerResidueCount(this)
}
return this._props.polymerResidueCount;
}
get polymerUnitCount() {
if (this._props.polymerUnitCount === -1) {
this._props.polymerUnitCount = getPolymerUnitCount(this)
}
return this._props.polymerUnitCount;
}
get uniqueElementCount() {
if (this._props.uniqueElementCount === -1) {
this._props.uniqueElementCount = getUniqueElementCount(this)
}
return this._props.uniqueElementCount;
}
/** Coarse structure, defined as Containing less than twice as many elements as polymer residues */
get isCoarse() {
const ec = this.elementCount
@@ -124,6 +145,7 @@ class Structure {
return this.computeHash();
}
/** Hash based on all unit.id values in the structure, reflecting the units transformation */
get transformHash() {
if (this._props.transformHash !== -1) return this._props.transformHash;
this._props.transformHash = hashFnv32a(this.units.map(u => u.id))
@@ -221,6 +243,13 @@ class Structure {
|| (this._props.uniqueAtomicResidueIndices = getUniqueAtomicResidueIndices(this));
}
get isAtomic() {
for (const u of this.units) {
if (u.kind !== Unit.Kind.Atomic) return false;
}
return true;
}
/**
* Provides mapping for serial element indices accross all units.
*
@@ -262,21 +291,19 @@ class Structure {
}
getModelIndex(m: Model) {
return this.model
return this.models.indexOf(m)
}
private initUnits(units: ArrayLike<Unit>) {
const unitMap = IntMap.Mutable<Unit>();
const unitIndexMap = IntMap.Mutable<number>();
let elementCount = 0;
let polymerResidueCount = 0;
let isSorted = true;
let lastId = units.length > 0 ? units[0].id : 0;
for (let i = 0, _i = units.length; i < _i; i++) {
const u = units[i];
unitMap.set(u.id, u);
elementCount += u.elements.length;
polymerResidueCount += u.polymerElements.length;
if (u.id < lastId) isSorted = false;
lastId = u.id;
}
@@ -285,7 +312,6 @@ class Structure {
unitIndexMap.set(units[i].id, i);
}
this._props.elementCount = elementCount;
this._props.polymerResidueCount = polymerResidueCount;
return { unitMap, unitIndexMap };
}
@@ -397,6 +423,33 @@ function getUniqueAtomicResidueIndices(structure: Structure): ReadonlyMap<UUID,
return ret;
}
function getUniqueElementCount(structure: Structure): number {
const { unitSymmetryGroups } = structure
let uniqueElementCount = 0
for (let i = 0, _i = unitSymmetryGroups.length; i < _i; i++) {
uniqueElementCount += unitSymmetryGroups[i].elements.length
}
return uniqueElementCount
}
function getPolymerResidueCount(structure: Structure): number {
const { units } = structure
let polymerResidueCount = 0
for (let i = 0, _i = units.length; i < _i; i++) {
polymerResidueCount += units[i].polymerElements.length;
}
return polymerResidueCount
}
function getPolymerUnitCount(structure: Structure): number {
const { units } = structure
let polymerUnitCount = 0
for (let i = 0, _i = units.length; i < _i; i++) {
if (units[i].polymerElements.length > 0) polymerUnitCount += 1
}
return polymerUnitCount
}
interface SerialMapping {
/** Cummulative count of elements for each unit */
unitElementCount: ArrayLike<number>
@@ -572,20 +625,23 @@ namespace Structure {
// keeps atoms of residues together
function partitionAtomicUnitByResidue(model: Model, indices: SortedArray, builder: StructureBuilder) {
model.atomicHierarchy.residueAtomSegments.offsets
const { residueAtomSegments } = model.atomicHierarchy
const startIndices: number[] = []
const endIndices: number[] = []
const residueIt = Segmentation.transientSegments(model.atomicHierarchy.residueAtomSegments, indices)
const residueIt = Segmentation.transientSegments(residueAtomSegments, indices)
while (residueIt.hasNext) {
const residueSegment = residueIt.move();
startIndices[startIndices.length] = indices[residueSegment.start]
endIndices[endIndices.length] = indices[residueSegment.end]
}
const firstResidueAtomCount = endIndices[0] - startIndices[0]
const gridCellCount = 512 * firstResidueAtomCount
const { x, y, z } = model.atomicConformation;
const lookup = GridLookup3D({ x, y, z, indices: SortedArray.ofSortedArray(startIndices) }, 8192);
const lookup = GridLookup3D({ x, y, z, indices: SortedArray.ofSortedArray(startIndices) }, gridCellCount);
const { offset, count, array } = lookup.buckets;
for (let i = 0, _i = offset.length; i < _i; i++) {

View File

@@ -9,15 +9,60 @@ import { Box3D, Sphere3D } from '../../../../mol-math/geometry';
import { BoundaryHelper } from '../../../../mol-math/geometry/boundary-helper';
import { Vec3 } from '../../../../mol-math/linear-algebra';
import Structure from '../structure';
import { CentroidHelper } from '../../../../mol-math/geometry/centroid-helper';
export type Boundary = { box: Box3D, sphere: Sphere3D }
const tmpBox = Box3D.empty()
const tmpSphere = Sphere3D.zero()
const tmpBox = Box3D.empty();
const tmpSphere = Sphere3D.zero();
const tmpVec = Vec3.zero();
const boundaryHelper = new BoundaryHelper();
const centroidHelper = new CentroidHelper();
// TODO: show this be enabled? does not solve problem with "renderable bounding spheres"
const CENTROID_COMPUTATION_THRESHOLD = -1;
export function computeStructureBoundary(s: Structure): Boundary {
if (s.elementCount <= CENTROID_COMPUTATION_THRESHOLD && s.isAtomic) return computeStructureBoundaryFromElements(s);
return computeStructureBoundaryFromUnits(s);
}
function computeStructureBoundaryFromElements(s: Structure): Boundary {
centroidHelper.reset();
const min = Vec3.create(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE)
const max = Vec3.create(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE)
const v = tmpVec;
for (const unit of s.units) {
const { x, y, z } = unit.conformation;
const elements = unit.elements;
for (let j = 0, _j = elements.length; j < _j; j++) {
const e = elements[j];
Vec3.set(v, x(e), y(e), z(e));
centroidHelper.includeStep(v);
Vec3.min(min, min, v);
Vec3.max(max, max, v);
};
}
centroidHelper.finishedIncludeStep();
for (const unit of s.units) {
const { x, y, z } = unit.conformation;
const elements = unit.elements;
for (let j = 0, _j = elements.length; j < _j; j++) {
const e = elements[j];
Vec3.set(v, x(e), y(e), z(e));
centroidHelper.radiusStep(v);
};
}
return { box: { min, max }, sphere: centroidHelper.getSphere() };
}
function computeStructureBoundaryFromUnits(s: Structure): Boundary {
const min = Vec3.create(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE)
const max = Vec3.create(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE)

View File

@@ -131,6 +131,12 @@ export class PluginContext {
substructureParent: new SubstructureParentHelper(this)
} as const;
/**
* Used to store application specific custom state which is then available
* to State Actions and similar constructs via the PluginContext.
*/
readonly customState: any = Object.create(null);
initViewer(canvas: HTMLCanvasElement, container: HTMLDivElement) {
try {
this.layout.setRoot(container);

View File

@@ -41,7 +41,7 @@ const Download = PluginStateTransform.BuiltIn({
update({ oldParams, newParams, b }) {
if (oldParams.url !== newParams.url || oldParams.isBinary !== newParams.isBinary) return StateTransformer.UpdateResult.Recreate;
if (oldParams.label !== newParams.label) {
(b.label as string) = newParams.label || newParams.url;
b.label = newParams.label || newParams.url;
return StateTransformer.UpdateResult.Updated;
}
return StateTransformer.UpdateResult.Unchanged;
@@ -237,6 +237,58 @@ const ParseDsn6 = PluginStateTransform.BuiltIn({
}
});
export { ImportString }
type ImportString = typeof ImportString
const ImportString = PluginStateTransform.BuiltIn({
name: 'import-string',
display: { name: 'Import String', description: 'Import given data as a string' },
from: SO.Root,
to: SO.Data.String,
params: {
data: PD.Value(''),
label: PD.Optional(PD.Text('')),
}
})({
apply({ params: { data, label } }) {
return new SO.Data.String(data, { label: label || '' });
},
update({ oldParams, newParams, b }) {
if (oldParams.data !== newParams.data) return StateTransformer.UpdateResult.Recreate;
if (oldParams.label !== newParams.label) {
b.label = newParams.label || '';
return StateTransformer.UpdateResult.Updated;
}
return StateTransformer.UpdateResult.Unchanged;
},
isSerializable: () => ({ isSerializable: false, reason: 'Cannot serialize user imported strings.' })
});
export { ImportJson }
type ImportJson = typeof ImportJson
const ImportJson = PluginStateTransform.BuiltIn({
name: 'import-json',
display: { name: 'Import JSON', description: 'Import given data as a JSON' },
from: SO.Root,
to: SO.Format.Json,
params: {
data: PD.Value<any>({}),
label: PD.Optional(PD.Text('')),
}
})({
apply({ params: { data, label } }) {
return new SO.Format.Json(data, { label: label || '' });
},
update({ oldParams, newParams, b }) {
if (oldParams.data !== newParams.data) return StateTransformer.UpdateResult.Recreate;
if (oldParams.label !== newParams.label) {
b.label = newParams.label || '';
return StateTransformer.UpdateResult.Updated;
}
return StateTransformer.UpdateResult.Unchanged;
},
isSerializable: () => ({ isSerializable: false, reason: 'Cannot serialize user imported JSON.' })
});
export { ParseJson }
type ParseJson = typeof ParseJson
const ParseJson = PluginStateTransform.BuiltIn({

View File

@@ -27,11 +27,18 @@ function opKey(l: StructureElement.Location) {
return `${ids.sort().join(',')}|${ncs}|${hkl}|${spgrOp}`
}
function splitModelEntityId(modelEntityId: string) {
const [ modelIdx, entityId ] = modelEntityId.split('|')
return [ parseInt(modelIdx), entityId ]
}
function getSequenceWrapper(state: SequenceViewState, structureSelection: StructureElementSelectionManager): SequenceWrapper.Any | undefined {
const { structure, entityId, invariantUnitId, operatorKey } = state
const { structure, modelEntityId, invariantUnitId, operatorKey } = state
const l = StructureElement.Location.create()
const [ modelIdx, entityId ] = splitModelEntityId(modelEntityId)
for (const unit of structure.units) {
StructureElement.Location.set(l, unit, unit.elements[0])
if (structure.getModelIndex(unit.model) !== modelIdx) continue
if (SP.entity.id(l) !== entityId) continue
if (unit.invariantId !== invariantUnitId) continue
if (opKey(l) !== operatorKey) continue
@@ -43,7 +50,7 @@ function getSequenceWrapper(state: SequenceViewState, structureSelection: Struct
}
}
function getEntityOptions(structure: Structure) {
function getModelEntityOptions(structure: Structure) {
const options: [string, string][] = []
const l = StructureElement.Location.create()
const seen = new Set<string>()
@@ -51,25 +58,33 @@ function getEntityOptions(structure: Structure) {
for (const unit of structure.units) {
StructureElement.Location.set(l, unit, unit.elements[0])
const id = SP.entity.id(l)
if (seen.has(id)) continue
const modelIdx = structure.getModelIndex(unit.model)
const key = `${modelIdx}|${id}`
if (seen.has(key)) continue
const label = `${id}: ${SP.entity.pdbx_description(l).join(', ')}`
options.push([ id, label ])
seen.add(id)
let description = SP.entity.pdbx_description(l).join(', ')
if (description.startsWith('Polymer ') && structure.models.length > 1) {
description += ` (${structure.models[modelIdx].entry})`
}
const label = `${id}: ${description}`
options.push([ key, label ])
seen.add(key)
}
if (options.length === 0) options.push(['', 'No entities'])
return options
}
function getUnitOptions(structure: Structure, entityId: string) {
function getUnitOptions(structure: Structure, modelEntityId: string) {
const options: [number, string][] = []
const l = StructureElement.Location.create()
const seen = new Set<number>()
const water = new Map<string, number>()
const [ modelIdx, entityId ] = splitModelEntityId(modelEntityId)
for (const unit of structure.units) {
StructureElement.Location.set(l, unit, unit.elements[0])
if (structure.getModelIndex(unit.model) !== modelIdx) continue
if (SP.entity.id(l) !== entityId) continue
const id = unit.invariantId
@@ -98,13 +113,15 @@ function getUnitOptions(structure: Structure, entityId: string) {
return options
}
function getOperatorOptions(structure: Structure, entityId: string, invariantUnitId: number) {
function getOperatorOptions(structure: Structure, modelEntityId: string, invariantUnitId: number) {
const options: [string, string][] = []
const l = StructureElement.Location.create()
const seen = new Set<string>()
const [ modelIdx, entityId ] = splitModelEntityId(modelEntityId)
for (const unit of structure.units) {
StructureElement.Location.set(l, unit, unit.elements[0])
if (structure.getModelIndex(unit.model) !== modelIdx) continue
if (SP.entity.id(l) !== entityId) continue
if (unit.invariantId !== invariantUnitId) continue
@@ -135,13 +152,13 @@ function getStructureOptions(state: State) {
type SequenceViewState = {
structure: Structure,
structureRef: string,
entityId: string,
modelEntityId: string,
invariantUnitId: number,
operatorKey: string
}
export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
state = { structure: Structure.Empty, structureRef: '', entityId: '', invariantUnitId: -1, operatorKey: '' }
state = { structure: Structure.Empty, structureRef: '', modelEntityId: '', invariantUnitId: -1, operatorKey: '' }
componentDidMount() {
if (this.plugin.state.dataState.select(StateSelection.Generators.rootsOfType(PSO.Molecule.Structure)).length > 0) this.setState(this.getInitialState())
@@ -180,18 +197,18 @@ export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
// TODO reuse selected values from previous state if applicable
const structureRef = getStructureOptions(this.plugin.state.dataState)[0][0]
const structure = this.getStructure(structureRef)
const entityId = getEntityOptions(structure)[0][0]
const invariantUnitId = getUnitOptions(structure, entityId)[0][0]
const operatorKey = getOperatorOptions(structure, entityId, invariantUnitId)[0][0]
return { structure, structureRef, entityId, invariantUnitId, operatorKey }
const modelEntityId = getModelEntityOptions(structure)[0][0]
const invariantUnitId = getUnitOptions(structure, modelEntityId)[0][0]
const operatorKey = getOperatorOptions(structure, modelEntityId, invariantUnitId)[0][0]
return { structure, structureRef, modelEntityId, invariantUnitId, operatorKey }
}
private get params() {
const { structure, entityId, invariantUnitId } = this.state
const { structure, modelEntityId, invariantUnitId } = this.state
const structureOptions = getStructureOptions(this.plugin.state.dataState)
const entityOptions = getEntityOptions(structure)
const unitOptions = getUnitOptions(structure, entityId)
const operatorOptions = getOperatorOptions(structure, entityId, invariantUnitId)
const entityOptions = getModelEntityOptions(structure)
const unitOptions = getUnitOptions(structure, modelEntityId)
const operatorOptions = getOperatorOptions(structure, modelEntityId, invariantUnitId)
return {
structure: PD.Select(structureOptions[0][0], structureOptions, { shortLabel: true }),
entity: PD.Select(entityOptions[0][0], entityOptions, { shortLabel: true }),
@@ -203,7 +220,7 @@ export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
private get values(): PD.Values<SequenceView['params']> {
return {
structure: this.state.structureRef,
entity: this.state.entityId,
entity: this.state.modelEntityId,
unit: this.state.invariantUnitId,
operator: this.state.operatorKey
}
@@ -217,18 +234,18 @@ export class SequenceView extends PluginUIComponent<{ }, SequenceViewState> {
case 'structure':
state.structureRef = p.value
state.structure = this.getStructure(p.value)
state.entityId = getEntityOptions(state.structure)[0][0]
state.invariantUnitId = getUnitOptions(state.structure, state.entityId)[0][0]
state.operatorKey = getOperatorOptions(state.structure, state.entityId, state.invariantUnitId)[0][0]
state.modelEntityId = getModelEntityOptions(state.structure)[0][0]
state.invariantUnitId = getUnitOptions(state.structure, state.modelEntityId)[0][0]
state.operatorKey = getOperatorOptions(state.structure, state.modelEntityId, state.invariantUnitId)[0][0]
break
case 'entity':
state.entityId = p.value
state.invariantUnitId = getUnitOptions(state.structure, state.entityId)[0][0]
state.operatorKey = getOperatorOptions(state.structure, state.entityId, state.invariantUnitId)[0][0]
state.modelEntityId = p.value
state.invariantUnitId = getUnitOptions(state.structure, state.modelEntityId)[0][0]
state.operatorKey = getOperatorOptions(state.structure, state.modelEntityId, state.invariantUnitId)[0][0]
break
case 'unit':
state.invariantUnitId = p.value
state.operatorKey = getOperatorOptions(state.structure, state.entityId, state.invariantUnitId)[0][0]
state.operatorKey = getOperatorOptions(state.structure, state.modelEntityId, state.invariantUnitId)[0][0]
break
case 'operator':
state.operatorKey = p.value