mirror of
https://github.com/molstar/molstar.git
synced 2026-06-05 05:44:23 +08:00
Compare commits
19 Commits
v2.1.0
...
v2.2.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
844c13cd35 | ||
|
|
d1c8b92fdf | ||
|
|
93d33bca80 | ||
|
|
6550e53414 | ||
|
|
96dddb0998 | ||
|
|
baa64d8109 | ||
|
|
2df145aa8f | ||
|
|
06b9c5f2de | ||
|
|
e03b689f27 | ||
|
|
e4cdcff3ee | ||
|
|
f73150d074 | ||
|
|
451dc12689 | ||
|
|
a3fb7762d8 | ||
|
|
3dfafc3202 | ||
|
|
4fcea991d3 | ||
|
|
0607ed46d1 | ||
|
|
30d6244e82 | ||
|
|
fab8c74365 | ||
|
|
1952922e4e |
@@ -6,6 +6,13 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- Add `tubularHelices` parameter to Cartoon representation
|
||||
- Add `SdfFormat` and update SDF parser to be able to parse data headers according to spec (hopefully :)) #230
|
||||
- Fix mononucleotides detected as polymer components (#229)
|
||||
- Set default outline scale back to 1
|
||||
- Improved DCD reader cell angle handling (intepret near 0 angles as 90 deg)
|
||||
- Handle more residue/atom names commonly used in force-fields
|
||||
- Add USDZ support to ``geo-export`` extension.
|
||||
|
||||
## [v2.1.0] - 2021-07-05
|
||||
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "2.1.0",
|
||||
"version": "2.2.0-dev.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "2.1.0",
|
||||
"version": "2.2.0-dev.1",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
|
||||
@@ -13,15 +13,17 @@ import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { StateSelection } from '../../mol-state';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { SetUtils } from '../../mol-util/set';
|
||||
import { ObjExporter } from './obj-exporter';
|
||||
import { GlbExporter } from './glb-exporter';
|
||||
import { ObjExporter } from './obj-exporter';
|
||||
import { StlExporter } from './stl-exporter';
|
||||
import { UsdzExporter } from './usdz-exporter';
|
||||
|
||||
export const GeometryParams = {
|
||||
format: PD.Select('glb', [
|
||||
['glb', 'glTF 2.0 Binary (.glb)'],
|
||||
['stl', 'Stl (.stl)'],
|
||||
['obj', 'Wavefront (.obj)']
|
||||
['obj', 'Wavefront (.obj)'],
|
||||
['usdz', 'Universal Scene Description (.usdz)']
|
||||
])
|
||||
};
|
||||
|
||||
@@ -44,11 +46,12 @@ export class GeometryControls extends PluginComponent {
|
||||
const renderObjects = this.plugin.canvas3d?.getRenderObjects()!;
|
||||
const filename = this.getFilename();
|
||||
|
||||
const boundingBox = Box3D.fromSphere3D(Box3D(), this.plugin.canvas3d?.boundingSphereVisible!);
|
||||
let renderObjectExporter: GlbExporter | ObjExporter | StlExporter;
|
||||
const style = getStyle(this.plugin.canvas3d?.props.renderer.style!);
|
||||
const boundingSphere = this.plugin.canvas3d?.boundingSphereVisible!;
|
||||
const boundingBox = Box3D.fromSphere3D(Box3D(), boundingSphere);
|
||||
let renderObjectExporter: GlbExporter | ObjExporter | StlExporter | UsdzExporter;
|
||||
switch (this.behaviors.params.value.format) {
|
||||
case 'glb':
|
||||
const style = getStyle(this.plugin.canvas3d?.props.renderer.style!);
|
||||
renderObjectExporter = new GlbExporter(style, boundingBox);
|
||||
break;
|
||||
case 'obj':
|
||||
@@ -57,6 +60,9 @@ export class GeometryControls extends PluginComponent {
|
||||
case 'stl':
|
||||
renderObjectExporter = new StlExporter(boundingBox);
|
||||
break;
|
||||
case 'usdz':
|
||||
renderObjectExporter = new UsdzExporter(style, boundingBox, boundingSphere.radius);
|
||||
break;
|
||||
default: throw new Error('Unsupported format.');
|
||||
}
|
||||
|
||||
|
||||
@@ -264,7 +264,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
|
||||
}
|
||||
}
|
||||
|
||||
getData() {
|
||||
async getData() {
|
||||
const binaryBufferLength = this.byteOffset;
|
||||
|
||||
const gltf = {
|
||||
@@ -334,7 +334,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
|
||||
}
|
||||
|
||||
async getBlob(ctx: RuntimeContext) {
|
||||
return new Blob([this.getData().glb], { type: 'model/gltf-binary' });
|
||||
return new Blob([(await this.getData()).glb], { type: 'model/gltf-binary' });
|
||||
}
|
||||
|
||||
constructor(private style: Style, boundingBox: Box3D) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
|
||||
*/
|
||||
|
||||
import { sort, arraySwap } from '../../mol-data/util';
|
||||
import { GraphicsRenderObject } from '../../mol-gl/render-object';
|
||||
import { MeshValues } from '../../mol-gl/renderable/mesh';
|
||||
import { LinesValues } from '../../mol-gl/renderable/lines';
|
||||
@@ -22,6 +23,7 @@ import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
|
||||
import { sizeDataFactor } from '../../mol-geo/geometry/size-data';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
import { Color } from '../../mol-util/color/color';
|
||||
import { decodeFloatRGB } from '../../mol-util/float-packing';
|
||||
import { RenderObjectExporter, RenderObjectExportData } from './render-object-exporter';
|
||||
|
||||
@@ -111,6 +113,70 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
|
||||
return interpolated.array;
|
||||
}
|
||||
|
||||
protected static quantizeColors(colorArray: Uint8Array, vertexCount: number) {
|
||||
if (vertexCount <= 1024) return;
|
||||
const rgb = Vec3();
|
||||
const min = Vec3();
|
||||
const max = Vec3();
|
||||
const sum = Vec3();
|
||||
const colorMap = new Map<Color, Color>();
|
||||
const colorComparers = [
|
||||
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[0] - Color.toVec3(rgb, colors[j])[0]),
|
||||
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[1] - Color.toVec3(rgb, colors[j])[1]),
|
||||
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[2] - Color.toVec3(rgb, colors[j])[2]),
|
||||
];
|
||||
|
||||
const medianCut = (colors: Color[], l: number, r: number, depth: number) => {
|
||||
if (l > r) return;
|
||||
if (l === r || depth >= 10) {
|
||||
// Find the average color.
|
||||
Vec3.set(sum, 0, 0, 0);
|
||||
for (let i = l; i <= r; ++i) {
|
||||
Color.toVec3(rgb, colors[i]);
|
||||
Vec3.add(sum, sum, rgb);
|
||||
}
|
||||
Vec3.round(rgb, Vec3.scale(rgb, sum, 1 / (r - l + 1)));
|
||||
const averageColor = Color.fromArray(rgb, 0);
|
||||
for (let i = l; i <= r; ++i) colorMap.set(colors[i], averageColor);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the color channel with the greatest range.
|
||||
Vec3.set(min, 255, 255, 255);
|
||||
Vec3.set(max, 0, 0, 0);
|
||||
for (let i = l; i <= r; ++i) {
|
||||
Color.toVec3(rgb, colors[i]);
|
||||
for (let j = 0; j < 3; ++j) {
|
||||
Vec3.min(min, min, rgb);
|
||||
Vec3.max(max, max, rgb);
|
||||
}
|
||||
}
|
||||
let k = 0;
|
||||
if (max[1] - min[1] > max[k] - min[k]) k = 1;
|
||||
if (max[2] - min[2] > max[k] - min[k]) k = 2;
|
||||
|
||||
sort(colors, l, r + 1, colorComparers[k], arraySwap);
|
||||
|
||||
const m = (l + r) >> 1;
|
||||
medianCut(colors, l, m, depth + 1);
|
||||
medianCut(colors, m + 1, r, depth + 1);
|
||||
};
|
||||
|
||||
// Create an array of unique colors and use the median cut algorithm.
|
||||
const colorSet = new Set<Color>();
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
colorSet.add(Color.fromArray(colorArray, i * 3));
|
||||
}
|
||||
const colors = Array.from(colorSet);
|
||||
medianCut(colors, 0, colors.length - 1, 0);
|
||||
|
||||
// Map actual colors to quantized colors.
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
const color = colorMap.get(Color.fromArray(colorArray, i * 3));
|
||||
Color.toArray(color!, colorArray, i * 3);
|
||||
}
|
||||
}
|
||||
|
||||
protected static getInstance(input: AddMeshInput, instanceIndex: number) {
|
||||
const { mesh, meshes } = input;
|
||||
if (mesh !== undefined) {
|
||||
@@ -278,7 +344,7 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
|
||||
}
|
||||
}
|
||||
|
||||
abstract getData(): D;
|
||||
abstract getData(ctx: RuntimeContext): Promise<D>;
|
||||
|
||||
abstract getBlob(ctx: RuntimeContext): Promise<Blob>;
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
|
||||
*/
|
||||
|
||||
import { sort, arraySwap } from '../../mol-data/util';
|
||||
import { asciiWrite } from '../../mol-io/common/ascii';
|
||||
import { Box3D } from '../../mol-math/geometry';
|
||||
import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
@@ -69,70 +68,6 @@ export class ObjExporter extends MeshExporter<ObjData> {
|
||||
}
|
||||
}
|
||||
|
||||
private static quantizeColors(colorArray: Uint8Array, vertexCount: number) {
|
||||
if (vertexCount <= 1024) return;
|
||||
const rgb = Vec3();
|
||||
const min = Vec3();
|
||||
const max = Vec3();
|
||||
const sum = Vec3();
|
||||
const colorMap = new Map<Color, Color>();
|
||||
const colorComparers = [
|
||||
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[0] - Color.toVec3(rgb, colors[j])[0]),
|
||||
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[1] - Color.toVec3(rgb, colors[j])[1]),
|
||||
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[2] - Color.toVec3(rgb, colors[j])[2]),
|
||||
];
|
||||
|
||||
const medianCut = (colors: Color[], l: number, r: number, depth: number) => {
|
||||
if (l > r) return;
|
||||
if (l === r || depth >= 10) {
|
||||
// Find the average color.
|
||||
Vec3.set(sum, 0, 0, 0);
|
||||
for (let i = l; i <= r; ++i) {
|
||||
Color.toVec3(rgb, colors[i]);
|
||||
Vec3.add(sum, sum, rgb);
|
||||
}
|
||||
Vec3.round(rgb, Vec3.scale(rgb, sum, 1 / (r - l + 1)));
|
||||
const averageColor = Color.fromArray(rgb, 0);
|
||||
for (let i = l; i <= r; ++i) colorMap.set(colors[i], averageColor);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the color channel with the greatest range.
|
||||
Vec3.set(min, 255, 255, 255);
|
||||
Vec3.set(max, 0, 0, 0);
|
||||
for (let i = l; i <= r; ++i) {
|
||||
Color.toVec3(rgb, colors[i]);
|
||||
for (let j = 0; j < 3; ++j) {
|
||||
Vec3.min(min, min, rgb);
|
||||
Vec3.max(max, max, rgb);
|
||||
}
|
||||
}
|
||||
let k = 0;
|
||||
if (max[1] - min[1] > max[k] - min[k]) k = 1;
|
||||
if (max[2] - min[2] > max[k] - min[k]) k = 2;
|
||||
|
||||
sort(colors, l, r + 1, colorComparers[k], arraySwap);
|
||||
|
||||
const m = (l + r) >> 1;
|
||||
medianCut(colors, l, m, depth + 1);
|
||||
medianCut(colors, m + 1, r, depth + 1);
|
||||
};
|
||||
|
||||
// Create an array of unique colors and use the median cut algorithm.
|
||||
const colorSet = new Set<Color>();
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
colorSet.add(Color.fromArray(colorArray, i * 3));
|
||||
}
|
||||
const colors = Array.from(colorSet);
|
||||
medianCut(colors, 0, colors.length - 1, 0);
|
||||
|
||||
// Map actual colors to quantized colors.
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
const color = colorMap.get(Color.fromArray(colorArray, i * 3));
|
||||
Color.toArray(color!, colorArray, i * 3);
|
||||
}
|
||||
}
|
||||
|
||||
protected async addMeshWithColors(input: AddMeshInput) {
|
||||
const { mesh, values, isGeoTexture, webgl, ctx } = input;
|
||||
|
||||
@@ -256,7 +191,7 @@ export class ObjExporter extends MeshExporter<ObjData> {
|
||||
}
|
||||
}
|
||||
|
||||
getData() {
|
||||
async getData() {
|
||||
return {
|
||||
obj: StringBuilder.getString(this.obj),
|
||||
mtl: StringBuilder.getString(this.mtl)
|
||||
@@ -264,7 +199,7 @@ export class ObjExporter extends MeshExporter<ObjData> {
|
||||
}
|
||||
|
||||
async getBlob(ctx: RuntimeContext) {
|
||||
const { obj, mtl } = this.getData();
|
||||
const { obj, mtl } = await this.getData();
|
||||
const objData = new Uint8Array(obj.length);
|
||||
asciiWrite(objData, obj);
|
||||
const mtlData = new Uint8Array(mtl.length);
|
||||
|
||||
@@ -9,12 +9,12 @@ import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
|
||||
export type RenderObjectExportData = {
|
||||
[k: string]: string | Uint8Array | undefined
|
||||
[k: string]: string | Uint8Array | ArrayBuffer | undefined
|
||||
}
|
||||
|
||||
export interface RenderObjectExporter<D extends RenderObjectExportData> {
|
||||
readonly fileExtension: string
|
||||
add(renderObject: GraphicsRenderObject, webgl: WebGLContext, ctx: RuntimeContext): Promise<void> | undefined
|
||||
getData(): D
|
||||
getData(ctx: RuntimeContext): Promise<D>
|
||||
getBlob(ctx: RuntimeContext): Promise<Blob>
|
||||
}
|
||||
@@ -89,7 +89,7 @@ export class StlExporter extends MeshExporter<StlData> {
|
||||
}
|
||||
}
|
||||
|
||||
getData() {
|
||||
async getData() {
|
||||
const stl = new Uint8Array(84 + 50 * this.triangleCount);
|
||||
|
||||
asciiWrite(stl, `Exported from Mol* ${PLUGIN_VERSION}`);
|
||||
@@ -106,7 +106,7 @@ export class StlExporter extends MeshExporter<StlData> {
|
||||
}
|
||||
|
||||
async getBlob(ctx: RuntimeContext) {
|
||||
return new Blob([this.getData().stl], { type: 'model/stl' });
|
||||
return new Blob([(await this.getData()).stl], { type: 'model/stl' });
|
||||
}
|
||||
|
||||
constructor(boundingBox: Box3D) {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { merge } from 'rxjs';
|
||||
import { CollapsableControls, CollapsableState } from '../../mol-plugin-ui/base';
|
||||
import { Button } from '../../mol-plugin-ui/controls/common';
|
||||
import { GetAppSvg, CubeSendSvg } from '../../mol-plugin-ui/controls/icons';
|
||||
import { GetAppSvg, CubeScanSvg, CubeSendSvg } from '../../mol-plugin-ui/controls/icons';
|
||||
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
|
||||
import { download } from '../../mol-util/download';
|
||||
import { GeometryParams, GeometryControls } from './controls';
|
||||
@@ -18,6 +18,7 @@ interface State {
|
||||
|
||||
export class GeometryExporterUI extends CollapsableControls<{}, State> {
|
||||
private _controls: GeometryControls | undefined;
|
||||
private isARSupported: boolean | undefined;
|
||||
|
||||
get controls() {
|
||||
return this._controls || (this._controls = new GeometryControls(this.plugin));
|
||||
@@ -32,6 +33,9 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
|
||||
}
|
||||
|
||||
protected renderControls(): JSX.Element {
|
||||
if (this.isARSupported === undefined) {
|
||||
this.isARSupported = !!document.createElement('a').relList?.supports?.('ar');
|
||||
}
|
||||
const ctrl = this.controls;
|
||||
return <>
|
||||
<ParameterControls
|
||||
@@ -45,6 +49,13 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
|
||||
disabled={this.state.busy || !this.plugin.canvas3d?.reprCount.value}>
|
||||
Save
|
||||
</Button>
|
||||
{this.isARSupported && ctrl.behaviors.params.value.format === 'usdz' &&
|
||||
<Button icon={CubeScanSvg}
|
||||
onClick={this.viewInAR} style={{ marginTop: 1 }}
|
||||
disabled={this.state.busy || !this.plugin.canvas3d?.reprCount.value}>
|
||||
View in AR
|
||||
</Button>
|
||||
}
|
||||
</>;
|
||||
}
|
||||
|
||||
@@ -75,4 +86,22 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
}
|
||||
|
||||
viewInAR = async () => {
|
||||
try {
|
||||
this.setState({ busy: true });
|
||||
const data = await this.controls.exportGeometry();
|
||||
this.setState({ busy: false });
|
||||
const a = document.createElement('a');
|
||||
a.rel = 'ar';
|
||||
a.href = URL.createObjectURL(data.blob);
|
||||
// For in-place viewing of USDZ on iOS, the link must contain a single child that is either an img or picture.
|
||||
// https://webkit.org/blog/8421/viewing-augmented-reality-assets-in-safari-for-ios/
|
||||
a.appendChild(document.createElement('img'));
|
||||
setTimeout(() => URL.revokeObjectURL(a.href), 4E4); // 40s
|
||||
setTimeout(() => a.dispatchEvent(new MouseEvent('click')));
|
||||
} catch {
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
262
src/extensions/geo-export/usdz-exporter.ts
Normal file
262
src/extensions/geo-export/usdz-exporter.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
|
||||
*/
|
||||
|
||||
import { Style } from '../../mol-gl/renderer';
|
||||
import { asciiWrite } from '../../mol-io/common/ascii';
|
||||
import { Box3D } from '../../mol-math/geometry';
|
||||
import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { PLUGIN_VERSION } from '../../mol-plugin/version';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
import { StringBuilder } from '../../mol-util';
|
||||
import { Color } from '../../mol-util/color/color';
|
||||
import { zip } from '../../mol-util/zip/zip';
|
||||
import { MeshExporter, AddMeshInput } from './mesh-exporter';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const v3fromArray = Vec3.fromArray;
|
||||
const v3transformMat4 = Vec3.transformMat4;
|
||||
const v3transformMat3 = Vec3.transformMat3;
|
||||
const mat3directionTransform = Mat3.directionTransform;
|
||||
|
||||
// https://graphics.pixar.com/usd/docs/index.html
|
||||
|
||||
export type UsdzData = {
|
||||
usdz: ArrayBuffer
|
||||
}
|
||||
|
||||
export class UsdzExporter extends MeshExporter<UsdzData> {
|
||||
readonly fileExtension = 'usdz';
|
||||
private meshes: string[] = [];
|
||||
private materials: string[] = [];
|
||||
private materialSet = new Set<number>();
|
||||
private centerTransform: Mat4;
|
||||
|
||||
private static getMaterialKey(color: Color, alpha: number) {
|
||||
return color * 256 + Math.round(alpha * 255);
|
||||
}
|
||||
|
||||
private addMaterial(color: Color, alpha: number) {
|
||||
const materialKey = UsdzExporter.getMaterialKey(color, alpha);
|
||||
if (this.materialSet.has(materialKey)) return;
|
||||
this.materialSet.add(materialKey);
|
||||
const [r, g, b] = Color.toRgbNormalized(color);
|
||||
this.materials.push(`
|
||||
def Material "material${materialKey}"
|
||||
{
|
||||
token outputs:surface.connect = </material${materialKey}/shader.outputs:surface>
|
||||
def Shader "shader"
|
||||
{
|
||||
uniform token info:id = "UsdPreviewSurface"
|
||||
color3f inputs:diffuseColor = (${r},${g},${b})
|
||||
float inputs:opacity = ${alpha}
|
||||
float inputs:metallic = ${this.style.metalness}
|
||||
float inputs:roughness = ${this.style.roughness}
|
||||
token outputs:surface
|
||||
}
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
protected async addMeshWithColors(input: AddMeshInput) {
|
||||
const { mesh, values, isGeoTexture, webgl, ctx } = input;
|
||||
|
||||
const t = Mat4();
|
||||
const n = Mat3();
|
||||
const tmpV = Vec3();
|
||||
const stride = isGeoTexture ? 4 : 3;
|
||||
|
||||
const groupCount = values.uGroupCount.ref.value;
|
||||
const colorType = values.dColorType.ref.value;
|
||||
const tColor = values.tColor.ref.value.array;
|
||||
const uAlpha = values.uAlpha.ref.value;
|
||||
const dTransparency = values.dTransparency.ref.value;
|
||||
const tTransparency = values.tTransparency.ref.value;
|
||||
const aTransform = values.aTransform.ref.value;
|
||||
const instanceCount = values.uInstanceCount.ref.value;
|
||||
|
||||
let interpolatedColors: Uint8Array;
|
||||
if (colorType === 'volume' || colorType === 'volumeInstance') {
|
||||
interpolatedColors = UsdzExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!);
|
||||
UsdzExporter.quantizeColors(interpolatedColors, mesh!.vertexCount);
|
||||
}
|
||||
|
||||
await ctx.update({ isIndeterminate: false, current: 0, max: instanceCount });
|
||||
|
||||
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
|
||||
if (ctx.shouldUpdate) await ctx.update({ current: instanceIndex + 1 });
|
||||
|
||||
const { vertices, normals, indices, groups, vertexCount, drawCount } = UsdzExporter.getInstance(input, instanceIndex);
|
||||
|
||||
Mat4.fromArray(t, aTransform, instanceIndex * 16);
|
||||
Mat4.mul(t, this.centerTransform, t);
|
||||
mat3directionTransform(n, t);
|
||||
|
||||
const vertexBuilder = StringBuilder.create();
|
||||
const normalBuilder = StringBuilder.create();
|
||||
const indexBuilder = StringBuilder.create();
|
||||
|
||||
// position
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t);
|
||||
StringBuilder.writeSafe(vertexBuilder, (i === 0) ? '(' : ',(');
|
||||
StringBuilder.writeFloat(vertexBuilder, tmpV[0], 10000);
|
||||
StringBuilder.writeSafe(vertexBuilder, ',');
|
||||
StringBuilder.writeFloat(vertexBuilder, tmpV[1], 10000);
|
||||
StringBuilder.writeSafe(vertexBuilder, ',');
|
||||
StringBuilder.writeFloat(vertexBuilder, tmpV[2], 10000);
|
||||
StringBuilder.writeSafe(vertexBuilder, ')');
|
||||
}
|
||||
|
||||
// normal
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
v3transformMat3(tmpV, v3fromArray(tmpV, normals, i * stride), n);
|
||||
StringBuilder.writeSafe(normalBuilder, (i === 0) ? '(' : ',(');
|
||||
StringBuilder.writeFloat(normalBuilder, tmpV[0], 100);
|
||||
StringBuilder.writeSafe(normalBuilder, ',');
|
||||
StringBuilder.writeFloat(normalBuilder, tmpV[1], 100);
|
||||
StringBuilder.writeSafe(normalBuilder, ',');
|
||||
StringBuilder.writeFloat(normalBuilder, tmpV[2], 100);
|
||||
StringBuilder.writeSafe(normalBuilder, ')');
|
||||
}
|
||||
|
||||
// face
|
||||
for (let i = 0; i < drawCount; ++i) {
|
||||
const v = isGeoTexture ? i : indices![i];
|
||||
if (i > 0) StringBuilder.writeSafe(indexBuilder, ',');
|
||||
StringBuilder.writeInteger(indexBuilder, v);
|
||||
}
|
||||
|
||||
// color
|
||||
const faceIndicesByMaterial = new Map<number, number[]>();
|
||||
for (let i = 0; i < drawCount; i += 3) {
|
||||
let color: Color;
|
||||
switch (colorType) {
|
||||
case 'uniform':
|
||||
color = Color.fromNormalizedArray(values.uColor.ref.value, 0);
|
||||
break;
|
||||
case 'instance':
|
||||
color = Color.fromArray(tColor, instanceIndex * 3);
|
||||
break;
|
||||
case 'group': {
|
||||
const group = isGeoTexture ? UsdzExporter.getGroup(groups, i) : groups[indices![i]];
|
||||
color = Color.fromArray(tColor, group * 3);
|
||||
break;
|
||||
}
|
||||
case 'groupInstance': {
|
||||
const group = isGeoTexture ? UsdzExporter.getGroup(groups, i) : groups[indices![i]];
|
||||
color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
|
||||
break;
|
||||
}
|
||||
case 'vertex':
|
||||
color = Color.fromArray(tColor, indices![i] * 3);
|
||||
break;
|
||||
case 'vertexInstance':
|
||||
color = Color.fromArray(tColor, (instanceIndex * vertexCount + indices![i]) * 3);
|
||||
break;
|
||||
case 'volume':
|
||||
color = Color.fromArray(interpolatedColors!, (isGeoTexture ? i : indices![i]) * 3);
|
||||
break;
|
||||
case 'volumeInstance':
|
||||
color = Color.fromArray(interpolatedColors!, (instanceIndex * vertexCount + (isGeoTexture ? i : indices![i])) * 3);
|
||||
break;
|
||||
default: throw new Error('Unsupported color type.');
|
||||
}
|
||||
|
||||
let alpha = uAlpha;
|
||||
if (dTransparency) {
|
||||
const group = isGeoTexture ? UsdzExporter.getGroup(groups, i) : groups[indices![i]];
|
||||
const transparency = tTransparency.array[instanceIndex * groupCount + group] / 255;
|
||||
alpha *= 1 - transparency;
|
||||
}
|
||||
|
||||
this.addMaterial(color, alpha);
|
||||
|
||||
const materialKey = UsdzExporter.getMaterialKey(color, alpha);
|
||||
let faceIndices = faceIndicesByMaterial.get(materialKey);
|
||||
if (faceIndices === undefined) {
|
||||
faceIndices = [];
|
||||
faceIndicesByMaterial.set(materialKey, faceIndices);
|
||||
}
|
||||
faceIndices.push(i / 3);
|
||||
}
|
||||
|
||||
// If this mesh uses only one material, bind it to the material directly.
|
||||
// Otherwise, use GeomSubsets to bind it to multiple materials.
|
||||
let materialBinding: string;
|
||||
if (faceIndicesByMaterial.size === 1) {
|
||||
const materialKey = faceIndicesByMaterial.keys().next().value;
|
||||
materialBinding = `rel material:binding = </material${materialKey}>`;
|
||||
} else {
|
||||
const geomSubsets: string[] = [];
|
||||
faceIndicesByMaterial.forEach((faceIndices: number[], materialKey: number) => {
|
||||
geomSubsets.push(`
|
||||
def GeomSubset "g${materialKey}"
|
||||
{
|
||||
uniform token elementType = "face"
|
||||
uniform token familyName = "materialBind"
|
||||
int[] indices = [${faceIndices.join(',')}]
|
||||
rel material:binding = </material${materialKey}>
|
||||
}
|
||||
`);
|
||||
});
|
||||
materialBinding = geomSubsets.join('');
|
||||
}
|
||||
|
||||
this.meshes.push(`
|
||||
def Mesh "mesh${this.meshes.length}"
|
||||
{
|
||||
int[] faceVertexCounts = [${new Array(drawCount / 3).fill(3).join(',')}]
|
||||
int[] faceVertexIndices = [${StringBuilder.getString(indexBuilder)}]
|
||||
point3f[] points = [${StringBuilder.getString(vertexBuilder)}]
|
||||
normal3f[] primvars:normals = [${StringBuilder.getString(normalBuilder)}] (
|
||||
interpolation = "vertex"
|
||||
)
|
||||
uniform token subdivisionScheme = "none"
|
||||
${materialBinding}
|
||||
}
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
async getData(ctx: RuntimeContext) {
|
||||
const header = `#usda 1.0
|
||||
(
|
||||
customLayerData = {
|
||||
string creator = "Mol* ${PLUGIN_VERSION}"
|
||||
}
|
||||
metersPerUnit = 1
|
||||
)
|
||||
`;
|
||||
const usda = [header, ...this.materials, ...this.meshes].join('');
|
||||
const usdaData = new Uint8Array(usda.length);
|
||||
asciiWrite(usdaData, usda);
|
||||
const zipDataObj = {
|
||||
['model.usda']: usdaData
|
||||
};
|
||||
return {
|
||||
usdz: await zip(ctx, zipDataObj, true)
|
||||
};
|
||||
}
|
||||
|
||||
async getBlob(ctx: RuntimeContext) {
|
||||
const { usdz } = await this.getData(ctx);
|
||||
return new Blob([usdz], { type: 'model/vnd.usdz+zip' });
|
||||
}
|
||||
|
||||
constructor(private style: Style, boundingBox: Box3D, radius: number) {
|
||||
super();
|
||||
const t = Mat4();
|
||||
// scale the model so that it fits within 1 meter
|
||||
Mat4.fromUniformScaling(t, Math.min(1 / (radius * 2), 1));
|
||||
// translate the model so that it sits on the ground plane (y = 0)
|
||||
Mat4.translate(t, t, Vec3.create(
|
||||
-(boundingBox.min[0] + boundingBox.max[0]) / 2,
|
||||
-boundingBox.min[1],
|
||||
-(boundingBox.min[2] + boundingBox.max[2]) / 2
|
||||
));
|
||||
this.centerTransform = t;
|
||||
}
|
||||
}
|
||||
@@ -250,7 +250,7 @@ export const PostprocessingParams = {
|
||||
}, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }),
|
||||
outline: PD.MappedStatic('off', {
|
||||
on: PD.Group({
|
||||
scale: PD.Numeric(2, { min: 1, max: 5, step: 1 }),
|
||||
scale: PD.Numeric(1, { min: 1, max: 5, step: 1 }),
|
||||
threshold: PD.Numeric(0.33, { min: 0.01, max: 1, step: 0.01 }),
|
||||
}),
|
||||
off: PD.Group({})
|
||||
|
||||
@@ -22,8 +22,8 @@ M END
|
||||
> <DATABASE_NAME>
|
||||
drugbank
|
||||
|
||||
> <SMILES>
|
||||
[O-]P([O-])([O-])=O
|
||||
> 5225 <TEST_FIELD>
|
||||
whatever
|
||||
|
||||
> <INCHI_IDENTIFIER>
|
||||
InChI=1S/H3O4P/c1-5(2,3)4/h(H3,1,2,3,4)/p-3
|
||||
@@ -362,22 +362,25 @@ describe('sdf reader', () => {
|
||||
expect(bonds.atomIdxB.value(3)).toBe(5);
|
||||
expect(bonds.order.value(3)).toBe(1);
|
||||
|
||||
expect(dataItems.dataHeader.value(0)).toBe('DATABASE_ID');
|
||||
expect(dataItems.dataHeader.value(0)).toBe('<DATABASE_ID>');
|
||||
expect(dataItems.data.value(0)).toBe('0');
|
||||
|
||||
expect(dataItems.dataHeader.value(1)).toBe('DATABASE_NAME');
|
||||
expect(dataItems.dataHeader.value(1)).toBe('<DATABASE_NAME>');
|
||||
expect(dataItems.data.value(1)).toBe('drugbank');
|
||||
|
||||
expect(dataItems.dataHeader.value(31)).toBe('SYNONYMS');
|
||||
expect(dataItems.dataHeader.value(2)).toBe('5225 <TEST_FIELD>');
|
||||
expect(dataItems.data.value(2)).toBe('whatever');
|
||||
|
||||
expect(dataItems.dataHeader.value(31)).toBe('<SYNONYMS>');
|
||||
expect(dataItems.data.value(31)).toBe('Orthophosphate; Phosphate');
|
||||
|
||||
expect(compound1.dataItems.data.value(0)).toBe('0');
|
||||
expect(compound2.dataItems.data.value(0)).toBe('1');
|
||||
|
||||
expect(compound3.dataItems.dataHeader.value(2)).toBe('PUBCHEM_CONFORMER_DIVERSEORDER');
|
||||
expect(compound3.dataItems.dataHeader.value(2)).toBe('<PUBCHEM_CONFORMER_DIVERSEORDER>');
|
||||
expect(compound3.dataItems.data.value(2)).toBe('1\n11\n10\n3\n15\n17\n13\n5\n16\n7\n14\n9\n8\n4\n18\n6\n12\n2');
|
||||
|
||||
expect(compound3.dataItems.dataHeader.value(21)).toBe('PUBCHEM_COORDINATE_TYPE');
|
||||
expect(compound3.dataItems.dataHeader.value(21)).toBe('<PUBCHEM_COORDINATE_TYPE>');
|
||||
expect(compound3.dataItems.data.value(21)).toBe('2\n5\n10');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,16 +13,20 @@ import { Tokenizer, TokenBuilder } from '../common/text/tokenizer';
|
||||
import { TokenColumnProvider as TokenColumn } from '../common/text/column/token';
|
||||
|
||||
/** http://c4.cabrillo.edu/404/ctfile.pdf - page 41 */
|
||||
export interface SdfFile {
|
||||
readonly compounds: {
|
||||
readonly molFile: MolFile,
|
||||
readonly dataItems: {
|
||||
readonly dataHeader: Column<string>,
|
||||
readonly data: Column<string>
|
||||
}
|
||||
}[]
|
||||
|
||||
export interface SdfFileCompound {
|
||||
readonly molFile: MolFile,
|
||||
readonly dataItems: {
|
||||
readonly dataHeader: Column<string>,
|
||||
readonly data: Column<string>
|
||||
}
|
||||
}
|
||||
|
||||
export interface SdfFile {
|
||||
readonly compounds: SdfFileCompound[]
|
||||
}
|
||||
|
||||
|
||||
const delimiter = '$$$$';
|
||||
function handleDataItems(tokenizer: Tokenizer): { dataHeader: Column<string>, data: Column<string> } {
|
||||
const dataHeader = TokenBuilder.create(tokenizer.data, 32);
|
||||
@@ -33,8 +37,8 @@ function handleDataItems(tokenizer: Tokenizer): { dataHeader: Column<string>, da
|
||||
if (line.startsWith(delimiter)) break;
|
||||
if (!line) continue;
|
||||
|
||||
if (line.startsWith('> <')) {
|
||||
TokenBuilder.add(dataHeader, tokenizer.tokenStart + 3, tokenizer.tokenEnd - 1);
|
||||
if (line.startsWith('> ')) {
|
||||
TokenBuilder.add(dataHeader, tokenizer.tokenStart + 2, tokenizer.tokenEnd);
|
||||
|
||||
Tokenizer.markLine(tokenizer);
|
||||
const start = tokenizer.tokenStart;
|
||||
@@ -42,7 +46,7 @@ function handleDataItems(tokenizer: Tokenizer): { dataHeader: Column<string>, da
|
||||
let added = false;
|
||||
while (tokenizer.position < tokenizer.length) {
|
||||
const line2 = Tokenizer.readLine(tokenizer);
|
||||
if (!line2 || line2.startsWith(delimiter) || line2.startsWith('> <')) {
|
||||
if (!line2 || line2.startsWith(delimiter) || line2.startsWith('> ')) {
|
||||
TokenBuilder.add(data, start, end);
|
||||
added = true;
|
||||
break;
|
||||
|
||||
@@ -13,21 +13,26 @@ import { mmCIF_chemComp_schema } from '../../../mol-io/reader/cif/schema/mmcif-e
|
||||
type Component = Table.Row<Pick<mmCIF_chemComp_schema, 'id' | 'name' | 'type'>>
|
||||
|
||||
const ProteinAtomIdsList = [
|
||||
new Set([ 'CA' ]),
|
||||
new Set([ 'C' ]),
|
||||
new Set([ 'N' ])
|
||||
new Set(['CA']),
|
||||
new Set(['C']),
|
||||
new Set(['N'])
|
||||
];
|
||||
const RnaAtomIdsList = [
|
||||
new Set([ 'P', 'O3\'', 'O3*' ]),
|
||||
new Set([ 'C4\'', 'C4*' ]),
|
||||
new Set([ 'O2\'', 'O2*', 'F2\'', 'F2*' ])
|
||||
new Set(['P', 'O3\'', 'O3*']),
|
||||
new Set(['C4\'', 'C4*']),
|
||||
new Set(['O2\'', 'O2*', 'F2\'', 'F2*'])
|
||||
];
|
||||
const DnaAtomIdsList = [
|
||||
new Set([ 'P', 'O3\'', 'O3*' ]),
|
||||
new Set([ 'C3\'', 'C3*' ]),
|
||||
new Set([ 'O2\'', 'O2*', 'F2\'', 'F2*' ])
|
||||
new Set(['P', 'O3\'', 'O3*']),
|
||||
new Set(['C3\'', 'C3*']),
|
||||
new Set(['O2\'', 'O2*', 'F2\'', 'F2*'])
|
||||
];
|
||||
|
||||
/** Used to reduce false positives for atom name-based type guessing */
|
||||
const NonPolymerNames = new Set([
|
||||
'FMN', 'NCN', 'FNS', 'FMA' // Mononucleotides
|
||||
]);
|
||||
|
||||
const StandardComponents = (function() {
|
||||
const map = new Map<string, Component>();
|
||||
const components: Component[] = [
|
||||
@@ -151,9 +156,11 @@ export class ComponentBuilder {
|
||||
this.set(StandardComponents.get(compId)!);
|
||||
} else if (WaterNames.has(compId)) {
|
||||
this.set({ id: compId, name: 'WATER', type: 'non-polymer' });
|
||||
} else if (NonPolymerNames.has(compId)) {
|
||||
this.set({ id: compId, name: this.namesMap.get(compId) || compId, type: 'non-polymer' });
|
||||
} else {
|
||||
const atomIds = this.getAtomIds(index);
|
||||
if (CharmmIonComponents.has(compId) && atomIds.size === 1) {
|
||||
if (atomIds.size === 1 && CharmmIonComponents.has(compId)) {
|
||||
this.set(CharmmIonComponents.get(compId)!);
|
||||
} else {
|
||||
const type = this.getType(atomIds);
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { degToRad, halfPI } from '../../mol-math/misc';
|
||||
import { Cell } from '../../mol-math/geometry/spacegroup/cell';
|
||||
import { Mutable } from '../../mol-util/type-helpers';
|
||||
import { EPSILON, equalEps } from '../../mol-math/linear-algebra/3d/common';
|
||||
|
||||
const charmmTimeUnitFactor = 20.45482949774598;
|
||||
|
||||
@@ -66,7 +67,12 @@ export function coordinatesFromDcd(dcdFile: DcdFile): Task<Coordinates> {
|
||||
} else {
|
||||
frame.cell = Cell.create(
|
||||
Vec3.create(c[0], c[2], c[5]),
|
||||
Vec3.create(degToRad(c[1]), degToRad(c[3]), degToRad(c[4]))
|
||||
// interpret angles very close to 0 as 90 deg
|
||||
Vec3.create(
|
||||
degToRad(equalEps(c[1], 0, EPSILON) ? 90 : c[1]),
|
||||
degToRad(equalEps(c[3], 0, EPSILON) ? 90 : c[3]),
|
||||
degToRad(equalEps(c[4], 0, EPSILON) ? 90 : c[4])
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { ModelFormat } from '../format';
|
||||
import { IndexPairBonds } from './property/bonds/index-pair';
|
||||
import { Trajectory } from '../../mol-model/structure';
|
||||
|
||||
async function getModels(mol: MolFile, ctx: RuntimeContext) {
|
||||
export async function getMolModels(mol: MolFile, format: ModelFormat<any> | undefined, ctx: RuntimeContext) {
|
||||
const { atoms, bonds } = mol;
|
||||
|
||||
const MOL = Column.ofConst('MOL', mol.atoms.count, Column.Schema.str);
|
||||
@@ -61,7 +61,7 @@ async function getModels(mol: MolFile, ctx: RuntimeContext) {
|
||||
atom_site
|
||||
});
|
||||
|
||||
const models = await createModels(basics, MolFormat.create(mol), ctx);
|
||||
const models = await createModels(basics, format ?? MolFormat.create(mol), ctx);
|
||||
|
||||
if (models.frameCount > 0) {
|
||||
const indexA = Column.ofIntArray(Column.mapToArray(bonds.atomIdxA, x => x - 1, Int32Array));
|
||||
@@ -91,5 +91,5 @@ namespace MolFormat {
|
||||
}
|
||||
|
||||
export function trajectoryFromMol(mol: MolFile): Task<Trajectory> {
|
||||
return Task.create('Parse MOL', ctx => getModels(mol, ctx));
|
||||
return Task.create('Parse MOL', ctx => getMolModels(mol, void 0, ctx));
|
||||
}
|
||||
|
||||
29
src/mol-model-formats/structure/sdf.ts
Normal file
29
src/mol-model-formats/structure/sdf.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { SdfFileCompound } from '../../mol-io/reader/sdf/parser';
|
||||
import { Trajectory } from '../../mol-model/structure';
|
||||
import { Task } from '../../mol-task';
|
||||
import { ModelFormat } from '../format';
|
||||
import { getMolModels } from './mol';
|
||||
|
||||
export { SdfFormat };
|
||||
|
||||
type SdfFormat = ModelFormat<SdfFileCompound>
|
||||
|
||||
namespace SdfFormat {
|
||||
export function is(x?: ModelFormat): x is SdfFormat {
|
||||
return x?.kind === 'sdf';
|
||||
}
|
||||
|
||||
export function create(mol: SdfFileCompound): SdfFormat {
|
||||
return { kind: 'sdf', name: mol.molFile.title, data: mol };
|
||||
}
|
||||
}
|
||||
|
||||
export function trajectoryFromSdf(mol: SdfFileCompound): Task<Trajectory> {
|
||||
return Task.create('Parse SDF', ctx => getMolModels(mol.molFile, SdfFormat.create(mol), ctx));
|
||||
}
|
||||
@@ -61,6 +61,14 @@ export function guessElementSymbolString(str: string) {
|
||||
) return str;
|
||||
}
|
||||
|
||||
if (l === 3) { // three chars
|
||||
if (str === 'SOD') return 'NA';
|
||||
if (str === 'POT') return 'K';
|
||||
if (str === 'CES') return 'CS';
|
||||
if (str === 'CAL') return 'CA';
|
||||
if (str === 'CLA') return 'CL';
|
||||
}
|
||||
|
||||
const c = str[0];
|
||||
if (c === 'C' || c === 'H' || c === 'N' || c === 'O' || c === 'P' || c === 'S') return c;
|
||||
|
||||
|
||||
31
src/mol-model-props/computed/helix-orientation.ts
Normal file
31
src/mol-model-props/computed/helix-orientation.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Model } from '../../mol-model/structure';
|
||||
import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
|
||||
import { CustomModelProperty } from '../common/custom-model-property';
|
||||
import { calcHelixOrientation, HelixOrientation } from './helix-orientation/helix-orientation';
|
||||
|
||||
export const HelixOrientationParams = { };
|
||||
export type HelixOrientationParams = typeof HelixOrientationParams
|
||||
export type HelixOrientationProps = PD.Values<HelixOrientationParams>
|
||||
|
||||
export type HelixOrientationValue = HelixOrientation;
|
||||
|
||||
export const HelixOrientationProvider: CustomModelProperty.Provider<HelixOrientationParams, HelixOrientationValue> = CustomModelProperty.createProvider({
|
||||
label: 'Helix Orientation',
|
||||
descriptor: CustomPropertyDescriptor({
|
||||
name: 'molstar_helix_orientation'
|
||||
}),
|
||||
type: 'dynamic',
|
||||
defaultParams: {},
|
||||
getParams: () => ({}),
|
||||
isApplicable: (data: Model) => true,
|
||||
obtain: async (ctx, data) => {
|
||||
return { value: calcHelixOrientation(data) };
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ElementIndex } from '../../../mol-model/structure';
|
||||
import { Segmentation } from '../../../mol-data/int/segmentation';
|
||||
import { SortedRanges } from '../../../mol-data/int/sorted-ranges';
|
||||
import { OrderedSet } from '../../../mol-data/int';
|
||||
import { Model } from '../../../mol-model/structure/model';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
|
||||
export interface HelixOrientation {
|
||||
centers: ArrayLike<number>
|
||||
}
|
||||
|
||||
/** Usees same definition as GROMACS' helixorient */
|
||||
export function calcHelixOrientation(model: Model): HelixOrientation {
|
||||
const { x, y, z } = model.atomicConformation;
|
||||
const { polymerType, traceElementIndex } = model.atomicHierarchy.derived.residue;
|
||||
const n = polymerType.length;
|
||||
|
||||
const elements = OrderedSet.ofBounds(0, model.atomicConformation.atomId.rowCount) as OrderedSet<ElementIndex>;
|
||||
const polymerIt = SortedRanges.transientSegments(model.atomicRanges.polymerRanges, elements);
|
||||
const residueIt = Segmentation.transientSegments(model.atomicHierarchy.residueAtomSegments, elements);
|
||||
|
||||
const centers = new Float32Array(n * 3);
|
||||
const axes = new Float32Array(n * 3);
|
||||
|
||||
let i = 0;
|
||||
let j = -1;
|
||||
let s = -1;
|
||||
|
||||
const a1 = Vec3();
|
||||
const a2 = Vec3();
|
||||
const a3 = Vec3();
|
||||
const a4 = Vec3();
|
||||
|
||||
const r12 = Vec3();
|
||||
const r23 = Vec3();
|
||||
const r34 = Vec3();
|
||||
|
||||
const v1 = Vec3();
|
||||
const v2 = Vec3();
|
||||
const vt = Vec3();
|
||||
|
||||
const diff13 = Vec3();
|
||||
const diff24 = Vec3();
|
||||
|
||||
const axis = Vec3();
|
||||
const prevAxis = Vec3();
|
||||
|
||||
while (polymerIt.hasNext) {
|
||||
const ps = polymerIt.move();
|
||||
residueIt.setSegment(ps);
|
||||
i = -1;
|
||||
s = -1;
|
||||
while (residueIt.hasNext) {
|
||||
i += 1;
|
||||
const { index } = residueIt.move();
|
||||
if (i === 0) s = index;
|
||||
|
||||
j = (index - 2);
|
||||
const j3 = j * 3;
|
||||
|
||||
Vec3.copy(a1, a2);
|
||||
Vec3.copy(a2, a3);
|
||||
Vec3.copy(a3, a4);
|
||||
|
||||
const eI = traceElementIndex[index];
|
||||
Vec3.set(a4, x[eI], y[eI], z[eI]);
|
||||
|
||||
if (i < 3) continue;
|
||||
|
||||
Vec3.sub(r12, a2, a1);
|
||||
Vec3.sub(r23, a3, a2);
|
||||
Vec3.sub(r34, a4, a3);
|
||||
|
||||
Vec3.sub(diff13, r12, r23);
|
||||
Vec3.sub(diff24, r23, r34);
|
||||
|
||||
Vec3.cross(axis, diff13, diff24);
|
||||
Vec3.normalize(axis, axis);
|
||||
Vec3.toArray(axis, axes, j3);
|
||||
|
||||
const tmp = Math.cos(Vec3.angle(diff13, diff24));
|
||||
|
||||
const diff13Length = Vec3.magnitude(diff13);
|
||||
const diff24Length = Vec3.magnitude(diff24);
|
||||
|
||||
const r = (
|
||||
Math.sqrt(diff24Length * diff13Length) /
|
||||
// clamp, to avoid numerical instabilities for when
|
||||
// angle between diff13 and diff24 is close to 0
|
||||
Math.max(2.0, 2.0 * (1.0 - tmp))
|
||||
);
|
||||
|
||||
Vec3.scale(v1, diff13, r / diff13Length);
|
||||
Vec3.sub(v1, a2, v1);
|
||||
Vec3.toArray(v1, centers, j3);
|
||||
|
||||
Vec3.scale(v2, diff24, r / diff24Length);
|
||||
Vec3.sub(v2, a3, v2);
|
||||
Vec3.toArray(v2, centers, j3 + 3);
|
||||
|
||||
Vec3.copy(prevAxis, axis);
|
||||
}
|
||||
|
||||
// calc axis as dir of second and third center pos
|
||||
// project first trace atom onto axis to get first center pos
|
||||
const s3 = s * 3;
|
||||
Vec3.fromArray(v1, centers, s3 + 3);
|
||||
Vec3.fromArray(v2, centers, s3 + 6);
|
||||
Vec3.normalize(axis, Vec3.sub(axis, v1, v2));
|
||||
const sI = traceElementIndex[s];
|
||||
Vec3.set(a1, x[sI], y[sI], z[sI]);
|
||||
Vec3.copy(vt, a1);
|
||||
Vec3.projectPointOnVector(vt, vt, axis, v1);
|
||||
Vec3.toArray(vt, centers, s3);
|
||||
|
||||
// calc axis as dir of n-1 and n-2 center pos
|
||||
// project last traceAtom onto axis to get last center pos
|
||||
const e = j + 2;
|
||||
const e3 = e * 3;
|
||||
Vec3.fromArray(v1, centers, e3 - 3);
|
||||
Vec3.fromArray(v2, centers, e3 - 6);
|
||||
Vec3.normalize(axis, Vec3.sub(axis, v1, v2));
|
||||
const eI = traceElementIndex[e];
|
||||
Vec3.set(a1, x[eI], y[eI], z[eI]);Vec3.copy(vt, a1);
|
||||
Vec3.projectPointOnVector(vt, vt, axis, v1);
|
||||
Vec3.toArray(vt, centers, e3);
|
||||
}
|
||||
|
||||
return {
|
||||
centers
|
||||
};
|
||||
}
|
||||
@@ -39,6 +39,18 @@ const ProteinOneLetterCodes: { [name: string]: AminoAlphabet } = {
|
||||
|
||||
'SEC': 'U', // as per IUPAC definition
|
||||
'PYL': 'O', // as per IUPAC definition
|
||||
|
||||
// charmm ff
|
||||
'HSD': 'H', 'HSE': 'H', 'HSP': 'H',
|
||||
'LSN': 'K',
|
||||
'ASPP': 'D',
|
||||
'GLUP': 'E',
|
||||
|
||||
// amber ff
|
||||
'HID': 'H', 'HIE': 'H', 'HIP': 'H',
|
||||
'LYN': 'K',
|
||||
'ASH': 'D',
|
||||
'GLH': 'E',
|
||||
};
|
||||
|
||||
const DnaOneLetterCodes: { [name: string]: NuclecicAlphabet } = {
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
const elm1 = [ 'H', 'C', 'O', 'N', 'S', 'P' ];
|
||||
const elm2 = [ 'NA', 'CL', 'FE' ];
|
||||
|
||||
function charAtIsNumber(str: string, index: number) {
|
||||
const code = str.charCodeAt(index);
|
||||
return code >= 48 && code <= 57;
|
||||
}
|
||||
|
||||
export function guessElement(str: string) {
|
||||
let at = str.trim().toUpperCase();
|
||||
|
||||
if (charAtIsNumber(at, 0)) at = at.substr(1);
|
||||
// parse again to check for a second integer
|
||||
if (charAtIsNumber(at, 0)) at = at.substr(1);
|
||||
const n = at.length;
|
||||
|
||||
if (n === 0) return '';
|
||||
if (n === 1) return at;
|
||||
if (n === 2) {
|
||||
if (elm2.indexOf(at) !== -1) return at;
|
||||
if (elm1.indexOf(at[0]) !== -1) return at[0];
|
||||
}
|
||||
if (n >= 3) {
|
||||
if (elm1.indexOf(at[0]) !== -1) return at[0];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@@ -99,7 +99,7 @@ export const PolymerTypeAtomRoleId: { [k in PolymerType]: { [k in AtomRole]: Set
|
||||
[PolymerType.Protein]: {
|
||||
trace: new Set(['CA']),
|
||||
directionFrom: new Set(['C']),
|
||||
directionTo: new Set(['O', 'OC1', 'O1', 'OX1', 'OXT']),
|
||||
directionTo: new Set(['O', 'OC1', 'O1', 'OX1', 'OXT', 'OT1']),
|
||||
backboneStart: new Set(['N']),
|
||||
backboneEnd: new Set(['C']),
|
||||
// CA1 is used e.g. in GFP chromophores
|
||||
@@ -246,8 +246,13 @@ 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
|
||||
'MSE', 'SEP', 'TPO', 'PTR', 'PCA' // common
|
||||
'MSE', 'SEP', 'TPO', 'PTR', 'PCA', // common from CCD
|
||||
|
||||
// charmm ff
|
||||
'HSD', 'HSE', 'HSP', 'LSN', 'ASPP', 'GLUP',
|
||||
|
||||
// amber ff
|
||||
'HID', 'HIE', 'HIP', 'LYN', 'ASH', 'GLH',
|
||||
]);
|
||||
export const AminoAcidNamesD = new Set([
|
||||
'DAL', // D-ALANINE
|
||||
@@ -586,7 +591,13 @@ export const MaxAsa = {
|
||||
'THR': 163.0,
|
||||
'TRP': 264.0,
|
||||
'TYR': 255.0,
|
||||
'VAL': 165.0
|
||||
'VAL': 165.0,
|
||||
|
||||
// charmm ff
|
||||
'HSD': 216.0, 'HSE': 216.0, 'HSP': 216.0,
|
||||
|
||||
// amber ff
|
||||
'HID': 216.0, 'HIE': 216.0, 'HIP': 216.0, 'ASH': 187.0, 'GLH': 214.0,
|
||||
};
|
||||
export const DefaultMaxAsa = 121.0;
|
||||
|
||||
@@ -680,6 +691,12 @@ export const ResidueHydrophobicity = {
|
||||
'THR': [0.14, 0.25, 0.11],
|
||||
'TRP': [-1.85, -2.09, -0.24],
|
||||
'TYR': [-0.94, -0.71, 0.23],
|
||||
'VAL': [0.07, -0.46, -0.53]
|
||||
'VAL': [0.07, -0.46, -0.53],
|
||||
|
||||
// charmm ff
|
||||
'HSD': [0.17, 0.11, -0.06], 'HSE': [0.17, 0.11, -0.06], 'HSP': [0.96, 2.33, 1.37],
|
||||
|
||||
// amber ff
|
||||
'HID': [0.17, 0.11, -0.06], 'HIE': [0.17, 0.11, -0.06], 'HIP': [0.96, 2.33, 1.37],
|
||||
};
|
||||
export const DefaultResidueHydrophobicity = [0.00, 0.00, 0.00];
|
||||
@@ -40,6 +40,7 @@ import { coordinatesFromXtc } from '../../mol-model-formats/structure/xtc';
|
||||
import { parseXyz } from '../../mol-io/reader/xyz/parser';
|
||||
import { trajectoryFromXyz } from '../../mol-model-formats/structure/xyz';
|
||||
import { parseSdf } from '../../mol-io/reader/sdf/parser';
|
||||
import { trajectoryFromSdf } from '../../mol-model-formats/structure/sdf';
|
||||
|
||||
export { CoordinatesFromDcd };
|
||||
export { CoordinatesFromXtc };
|
||||
@@ -308,8 +309,8 @@ const TrajectoryFromSDF = PluginStateTransform.BuiltIn({
|
||||
|
||||
const models: Model[] = [];
|
||||
|
||||
for (const { molFile } of parsed.result.compounds) {
|
||||
const traj = await trajectoryFromMol(molFile).runInContext(ctx);
|
||||
for (const compound of parsed.result.compounds) {
|
||||
const traj = await trajectoryFromSdf(compound).runInContext(ctx);
|
||||
for (let i = 0; i < traj.frameCount; i++) {
|
||||
models.push(await Task.resolveInContext(traj.getFrameAtIndex(i), ctx));
|
||||
}
|
||||
|
||||
@@ -41,6 +41,9 @@ export function MoleculeSvg() { return _Molecule; }
|
||||
const _CubeOutline = <svg width='24px' height='24px' viewBox='0 0 24 24' strokeWidth='0.1px'><path d="M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L6.04,7.5L12,10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V9.21L13,12.58V19.29L19,15.91Z" /></svg>;
|
||||
export function CubeOutlineSvg() { return _CubeOutline; }
|
||||
|
||||
const _CubeScan = <svg width='24px' height='24px' viewBox='0 0 24 24' strokeWidth='0.1px'><path d="M17,22V20H20V17H22V20.5C22,20.89 21.84,21.24 21.54,21.54C21.24,21.84 20.89,22 20.5,22H17M7,22H3.5C3.11,22 2.76,21.84 2.46,21.54C2.16,21.24 2,20.89 2,20.5V17H4V20H7V22M17,2H20.5C20.89,2 21.24,2.16 21.54,2.46C21.84,2.76 22,3.11 22,3.5V7H20V4H17V2M7,2V4H4V7H2V3.5C2,3.11 2.16,2.76 2.46,2.46C2.76,2.16 3.11,2 3.5,2H7M13,17.25L17,14.95V10.36L13,12.66V17.25M12,10.92L16,8.63L12,6.28L8,8.63L12,10.92M7,14.95L11,17.25V12.66L7,10.36V14.95M18.23,7.59C18.73,7.91 19,8.34 19,8.91V15.23C19,15.8 18.73,16.23 18.23,16.55L12.75,19.73C12.25,20.05 11.75,20.05 11.25,19.73L5.77,16.55C5.27,16.23 5,15.8 5,15.23V8.91C5,8.34 5.27,7.91 5.77,7.59L11.25,4.41C11.5,4.28 11.75,4.22 12,4.22C12.25,4.22 12.5,4.28 12.75,4.41L18.23,7.59Z" /></svg>;
|
||||
export function CubeScanSvg() { return _CubeScan; }
|
||||
|
||||
const _CubeSend = <svg width='24px' height='24px' viewBox='0 0 24 24' strokeWidth='0.1px'><path d="M16,4L9,8.04V15.96L16,20L23,15.96V8.04M16,6.31L19.8,8.5L16,10.69L12.21,8.5M0,7V9H7V7M11,10.11L15,12.42V17.11L11,14.81M21,10.11V14.81L17,17.11V12.42M2,11V13H7V11M4,15V17H7V15" /></svg>;
|
||||
export function CubeSendSvg() { return _CubeSend; }
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018 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>
|
||||
*/
|
||||
@@ -17,6 +17,7 @@ import { PolymerGapParams, PolymerGapVisual } from '../visual/polymer-gap-cylind
|
||||
import { PolymerTraceParams, PolymerTraceVisual } from '../visual/polymer-trace-mesh';
|
||||
import { SecondaryStructureProvider } from '../../../mol-model-props/computed/secondary-structure';
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
import { HelixOrientationProvider } from '../../../mol-model-props/computed/helix-orientation';
|
||||
|
||||
const CartoonVisuals = {
|
||||
'polymer-trace': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, PolymerTraceParams>) => UnitsRepresentation('Polymer trace mesh', ctx, getParams, PolymerTraceVisual),
|
||||
@@ -67,7 +68,17 @@ export const CartoonRepresentationProvider = StructureRepresentationProvider({
|
||||
defaultSizeTheme: { name: 'uniform' },
|
||||
isApplicable: (structure: Structure) => structure.polymerResidueCount > 0,
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, structure: Structure) => SecondaryStructureProvider.attach(ctx, structure, void 0, true),
|
||||
detach: (data) => SecondaryStructureProvider.ref(data, false)
|
||||
attach: async (ctx: CustomProperty.Context, structure: Structure) => {
|
||||
await SecondaryStructureProvider.attach(ctx, structure, void 0, true);
|
||||
for (const m of structure.models) {
|
||||
await HelixOrientationProvider.attach(ctx, m, void 0, true);
|
||||
}
|
||||
},
|
||||
detach: (data) => {
|
||||
SecondaryStructureProvider.ref(data, false);
|
||||
for (const m of data.models) {
|
||||
HelixOrientationProvider.ref(m, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 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>
|
||||
*/
|
||||
@@ -27,6 +27,7 @@ export const PolymerTraceMeshParams = {
|
||||
sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }),
|
||||
aspectRatio: PD.Numeric(5, { min: 0.1, max: 10, step: 0.1 }),
|
||||
arrowFactor: PD.Numeric(1.5, { min: 0, max: 3, step: 0.1 }),
|
||||
tubularHelices: PD.Boolean(false),
|
||||
detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }, BaseGeometry.CustomQualityParamInfo),
|
||||
linearSegments: PD.Numeric(8, { min: 1, max: 48, step: 1 }, BaseGeometry.CustomQualityParamInfo),
|
||||
radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }, BaseGeometry.CustomQualityParamInfo)
|
||||
@@ -40,7 +41,7 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
|
||||
const polymerElementCount = unit.polymerElements.length;
|
||||
|
||||
if (!polymerElementCount) return Mesh.createEmpty(mesh);
|
||||
const { sizeFactor, detail, linearSegments, radialSegments, aspectRatio, arrowFactor } = props;
|
||||
const { sizeFactor, detail, linearSegments, radialSegments, aspectRatio, arrowFactor, tubularHelices } = props;
|
||||
|
||||
const vertexCount = linearSegments * radialSegments * polymerElementCount + (radialSegments + 1) * polymerElementCount * 2;
|
||||
const builderState = MeshBuilder.createState(vertexCount, vertexCount / 10, mesh);
|
||||
@@ -50,7 +51,7 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
|
||||
const { curvePoints, normalVectors, binormalVectors, widthValues, heightValues } = state;
|
||||
|
||||
let i = 0;
|
||||
const polymerTraceIt = PolymerTraceIterator(unit, structure);
|
||||
const polymerTraceIt = PolymerTraceIterator(unit, structure, { ignoreSecondaryStructure: false, useHelixOrientation: tubularHelices });
|
||||
while (polymerTraceIt.hasNext) {
|
||||
const v = polymerTraceIt.move();
|
||||
builderState.currentGroup = i;
|
||||
@@ -58,7 +59,7 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
|
||||
const isNucleicType = isNucleic(v.moleculeType);
|
||||
const isSheet = SecondaryStructureType.is(v.secStrucType, SecondaryStructureType.Flag.Beta);
|
||||
const isHelix = SecondaryStructureType.is(v.secStrucType, SecondaryStructureType.Flag.Helix);
|
||||
const tension = isHelix ? HelixTension : StandardTension;
|
||||
const tension = isHelix && !tubularHelices ? HelixTension : StandardTension;
|
||||
const shift = isNucleicType ? NucleicShift : StandardShift;
|
||||
|
||||
interpolateCurveSegment(state, v, tension, shift);
|
||||
@@ -112,9 +113,19 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
|
||||
} else {
|
||||
let h0: number, h1: number, h2: number;
|
||||
if (isHelix && !v.isCoarseBackbone) {
|
||||
h0 = w0 * aspectRatio;
|
||||
h1 = w1 * aspectRatio;
|
||||
h2 = w2 * aspectRatio;
|
||||
if (tubularHelices) {
|
||||
w0 *= aspectRatio * 1.5;
|
||||
w1 *= aspectRatio * 1.5;
|
||||
w2 *= aspectRatio * 1.5;
|
||||
|
||||
h0 = w0;
|
||||
h1 = w1;
|
||||
h2 = w2;
|
||||
} else {
|
||||
h0 = w0 * aspectRatio;
|
||||
h1 = w1 * aspectRatio;
|
||||
h2 = w2 * aspectRatio;
|
||||
}
|
||||
} else if (isNucleicType && !v.isCoarseBackbone) {
|
||||
h0 = w0 * aspectRatio;
|
||||
h1 = w1 * aspectRatio;
|
||||
@@ -172,6 +183,7 @@ export function PolymerTraceVisual(materialId: number): UnitsVisual<PolymerTrace
|
||||
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<PolymerTraceParams>, currentProps: PD.Values<PolymerTraceParams>, newTheme: Theme, currentTheme: Theme, newStructureGroup: StructureGroup, currentStructureGroup: StructureGroup) => {
|
||||
state.createGeometry = (
|
||||
newProps.sizeFactor !== currentProps.sizeFactor ||
|
||||
newProps.tubularHelices !== currentProps.tubularHelices ||
|
||||
newProps.detail !== currentProps.detail ||
|
||||
newProps.linearSegments !== currentProps.linearSegments ||
|
||||
newProps.radialSegments !== currentProps.radialSegments ||
|
||||
|
||||
@@ -46,7 +46,7 @@ function createPolymerTubeMesh(ctx: VisualContext, unit: Unit, structure: Struct
|
||||
const { curvePoints, normalVectors, binormalVectors, widthValues, heightValues } = state;
|
||||
|
||||
let i = 0;
|
||||
const polymerTraceIt = PolymerTraceIterator(unit, structure, true);
|
||||
const polymerTraceIt = PolymerTraceIterator(unit, structure, { ignoreSecondaryStructure: true });
|
||||
while (polymerTraceIt.hasNext) {
|
||||
const v = polymerTraceIt.move();
|
||||
builderState.currentGroup = i;
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -14,14 +14,31 @@ import { CoarseSphereConformation, CoarseGaussianConformation } from '../../../.
|
||||
import { getPolymerRanges } from '../polymer';
|
||||
import { AtomicConformation } from '../../../../../mol-model/structure/model/properties/atomic';
|
||||
import { SecondaryStructureProvider } from '../../../../../mol-model-props/computed/secondary-structure';
|
||||
import { HelixOrientationProvider } from '../../../../../mol-model-props/computed/helix-orientation';
|
||||
import { SecondaryStructure } from '../../../../../mol-model/structure/model/properties/seconday-structure';
|
||||
|
||||
function isHelixSS(ss: SecondaryStructureType.Flag) {
|
||||
return SecondaryStructureType.is(ss, SecondaryStructureType.Flag.Helix);
|
||||
}
|
||||
|
||||
function isSheetSS(ss: SecondaryStructureType.Flag) {
|
||||
return SecondaryStructureType.is(ss, SecondaryStructureType.Flag.Beta);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
type PolymerTraceIteratorOptions = {
|
||||
ignoreSecondaryStructure?: boolean,
|
||||
useHelixOrientation?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over individual residues/coarse elements in polymers of a unit while
|
||||
* providing information about the neighbourhood in the underlying model for drawing splines
|
||||
*/
|
||||
export function PolymerTraceIterator(unit: Unit, structure: Structure, ignoreSecondaryStructure = false): Iterator<PolymerTraceElement> {
|
||||
export function PolymerTraceIterator(unit: Unit, structure: Structure, options: PolymerTraceIteratorOptions = {}): Iterator<PolymerTraceElement> {
|
||||
switch (unit.kind) {
|
||||
case Unit.Kind.Atomic: return new AtomicPolymerTraceIterator(unit, structure, ignoreSecondaryStructure);
|
||||
case Unit.Kind.Atomic: return new AtomicPolymerTraceIterator(unit, structure, options);
|
||||
case Unit.Kind.Spheres:
|
||||
case Unit.Kind.Gaussians:
|
||||
return new CoarsePolymerTraceIterator(unit, structure);
|
||||
@@ -91,6 +108,8 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
|
||||
private directionToElementIndex: ArrayLike<ElementIndex | -1>
|
||||
private moleculeType: ArrayLike<MoleculeType>
|
||||
private atomicConformation: AtomicConformation
|
||||
private secondaryStructure: SecondaryStructure | undefined
|
||||
private helixOrientationCenters: ArrayLike<number> | undefined
|
||||
|
||||
private p0 = Vec3()
|
||||
private p1 = Vec3()
|
||||
@@ -107,7 +126,7 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
|
||||
|
||||
hasNext: boolean = false;
|
||||
|
||||
private pos(target: Vec3, index: number) {
|
||||
private atomicPos(target: Vec3, index: ElementIndex | -1) {
|
||||
if (index !== -1) {
|
||||
target[0] = this.atomicConformation.x[index];
|
||||
target[1] = this.atomicConformation.y[index];
|
||||
@@ -115,6 +134,15 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
|
||||
}
|
||||
}
|
||||
|
||||
private pos(target: Vec3, residueIndex: ResidueIndex, ss: SecondaryStructureType.Flag) {
|
||||
const index = this.traceElementIndex[residueIndex];
|
||||
if (this.helixOrientationCenters && isHelixSS(ss)) {
|
||||
Vec3.fromArray(target, this.helixOrientationCenters, residueIndex * 3);
|
||||
} else {
|
||||
this.atomicPos(target, index);
|
||||
}
|
||||
}
|
||||
|
||||
private updateResidueSegmentRange(polymerSegment: Segmentation.Segment<number>) {
|
||||
const { index } = this.residueAtomSegments;
|
||||
this.residueSegmentMin = index[this.polymerRanges[polymerSegment.index * 2]];
|
||||
@@ -140,23 +168,31 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
|
||||
return residueIndex as ResidueIndex;
|
||||
}
|
||||
|
||||
private getSecStruc: (residueIndex: ResidueIndex) => SecondaryStructureType.Flag
|
||||
private getSecStruc(residueIndex: ResidueIndex): SecondaryStructureType.Flag {
|
||||
if (this.secondaryStructure) {
|
||||
const { type, getIndex } = this.secondaryStructure;
|
||||
const ss = type[getIndex(residueIndex)];
|
||||
// normalize helix-type
|
||||
return isHelixSS(ss) ? SecondaryStructureType.Flag.Helix : ss;
|
||||
} else {
|
||||
return SecStrucTypeNA;
|
||||
}
|
||||
}
|
||||
|
||||
private setControlPoint(out: Vec3, p1: Vec3, p2: Vec3, p3: Vec3, residueIndex: ResidueIndex) {
|
||||
const ss = this.getSecStruc(residueIndex);
|
||||
if (SecondaryStructureType.is(ss, SecondaryStructureType.Flag.Beta)) {
|
||||
private setControlPoint(out: Vec3, p1: Vec3, p2: Vec3, p3: Vec3, ss: SecondaryStructureType.Flag) {
|
||||
if (isSheetSS(ss) || (this.helixOrientationCenters && isHelixSS(ss))) {
|
||||
Vec3.scale(out, Vec3.add(out, p1, Vec3.add(out, p3, Vec3.add(out, p2, p2))), 1 / 4);
|
||||
} else {
|
||||
Vec3.copy(out, p2);
|
||||
}
|
||||
}
|
||||
|
||||
private setFromToVector(out: Vec3, residueIndex: ResidueIndex) {
|
||||
if (this.value.isCoarseBackbone) {
|
||||
private setFromToVector(out: Vec3, residueIndex: ResidueIndex, ss: SecondaryStructureType.Flag) {
|
||||
if (this.value.isCoarseBackbone || (this.helixOrientationCenters && isHelixSS(ss))) {
|
||||
Vec3.set(out, 1, 0, 0);
|
||||
} else {
|
||||
this.pos(tmpVecA, this.directionFromElementIndex[residueIndex]);
|
||||
this.pos(tmpVecB, this.directionToElementIndex[residueIndex]);
|
||||
this.atomicPos(tmpVecA, this.directionFromElementIndex[residueIndex]);
|
||||
this.atomicPos(tmpVecB, this.directionToElementIndex[residueIndex]);
|
||||
Vec3.sub(out, tmpVecB, tmpVecA);
|
||||
}
|
||||
}
|
||||
@@ -203,7 +239,7 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
|
||||
|
||||
this.prevCoarseBackbone = this.currCoarseBackbone;
|
||||
this.currCoarseBackbone = this.nextCoarseBackbone;
|
||||
this.nextCoarseBackbone = residueIndex === residueIndexNext1 ? false : (this.directionFromElementIndex[residueIndexNext1] === -1 || this.directionToElementIndex[residueIndexNext1] === -1);
|
||||
this.nextCoarseBackbone = this.directionFromElementIndex[residueIndexNext1] === -1 || this.directionToElementIndex[residueIndexNext1] === -1;
|
||||
|
||||
value.secStrucType = this.currSecStrucType;
|
||||
value.secStrucFirst = this.prevSecStrucType !== this.currSecStrucType;
|
||||
@@ -214,7 +250,6 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
|
||||
value.first = residueIndex === this.residueSegmentMin;
|
||||
value.last = residueIndex === this.residueSegmentMax;
|
||||
value.moleculeType = this.moleculeType[residueIndex];
|
||||
value.isCoarseBackbone = this.directionFromElementIndex[residueIndex] === -1 || this.directionToElementIndex[residueIndex] === -1;
|
||||
|
||||
value.initial = residueIndex === residueIndexPrev1;
|
||||
value.final = residueIndex === residueIndexNext1;
|
||||
@@ -223,53 +258,132 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
|
||||
value.center.element = this.traceElementIndex[residueIndex];
|
||||
value.centerNext.element = this.traceElementIndex[residueIndexNext1];
|
||||
|
||||
this.pos(this.p0, this.traceElementIndex[residueIndexPrev3]);
|
||||
this.pos(this.p1, this.traceElementIndex[residueIndexPrev2]);
|
||||
this.pos(this.p2, this.traceElementIndex[residueIndexPrev1]);
|
||||
this.pos(this.p3, this.traceElementIndex[residueIndex]);
|
||||
this.pos(this.p4, this.traceElementIndex[residueIndexNext1]);
|
||||
this.pos(this.p5, this.traceElementIndex[residueIndexNext2]);
|
||||
this.pos(this.p6, this.traceElementIndex[residueIndexNext3]);
|
||||
const ssPrev3 = this.getSecStruc(residueIndexPrev3);
|
||||
const ssPrev2 = this.getSecStruc(residueIndexPrev2);
|
||||
const ssPrev1 = this.getSecStruc(residueIndexPrev1);
|
||||
const ss = this.getSecStruc(residueIndex);
|
||||
const ssNext1 = this.getSecStruc(residueIndexNext1);
|
||||
const ssNext2 = this.getSecStruc(residueIndexNext2);
|
||||
const ssNext3 = this.getSecStruc(residueIndexNext3);
|
||||
|
||||
this.setFromToVector(this.d01, residueIndexPrev1);
|
||||
this.setFromToVector(this.d12, residueIndex);
|
||||
this.setFromToVector(this.d23, residueIndexNext1);
|
||||
this.setFromToVector(this.d34, residueIndexNext2);
|
||||
this.pos(this.p0, residueIndexPrev3, ssPrev3);
|
||||
this.pos(this.p1, residueIndexPrev2, ssPrev2);
|
||||
this.pos(this.p2, residueIndexPrev1, ssPrev1);
|
||||
this.pos(this.p3, residueIndex, ss);
|
||||
this.pos(this.p4, residueIndexNext1, ssNext1);
|
||||
this.pos(this.p5, residueIndexNext2, ssNext2);
|
||||
this.pos(this.p6, residueIndexNext3, ssNext3);
|
||||
|
||||
const isHelixPrev3 = isHelixSS(ssPrev3);
|
||||
const isHelixPrev2 = isHelixSS(ssPrev2);
|
||||
const isHelixPrev1 = isHelixSS(ssPrev1);
|
||||
const isHelix = isHelixSS(ss);
|
||||
const isHelixNext1 = isHelixSS(ssNext1);
|
||||
const isHelixNext2 = isHelixSS(ssNext2);
|
||||
const isHelixNext3 = isHelixSS(ssNext3);
|
||||
|
||||
// handle positions for tubular helices
|
||||
if (this.helixOrientationCenters) {
|
||||
if (isHelix !== isHelixPrev1) {
|
||||
if (isHelix) {
|
||||
Vec3.copy(this.p0, this.p3);
|
||||
Vec3.copy(this.p1, this.p3);
|
||||
Vec3.copy(this.p2, this.p3);
|
||||
} else if(isHelixPrev1) {
|
||||
Vec3.scale(tmpDir, Vec3.sub(tmpDir, this.p2, this.p3), 2);
|
||||
Vec3.add(this.p2, this.p3, tmpDir);
|
||||
Vec3.add(this.p1, this.p2, tmpDir);
|
||||
Vec3.add(this.p0, this.p1, tmpDir);
|
||||
}
|
||||
} else if (isHelix !== isHelixPrev2) {
|
||||
if (isHelix) {
|
||||
Vec3.copy(this.p0, this.p2);
|
||||
Vec3.copy(this.p1, this.p2);
|
||||
} else if(isHelixPrev2) {
|
||||
Vec3.scale(tmpDir, Vec3.sub(tmpDir, this.p1, this.p2), 2);
|
||||
Vec3.add(this.p1, this.p2, tmpDir);
|
||||
Vec3.add(this.p0, this.p1, tmpDir);
|
||||
}
|
||||
} else if (isHelix !== isHelixPrev3) {
|
||||
if (isHelix) {
|
||||
Vec3.copy(this.p0, this.p1);
|
||||
} else if(isHelixPrev3) {
|
||||
Vec3.scale(tmpDir, Vec3.sub(tmpDir, this.p0, this.p1), 2);
|
||||
Vec3.add(this.p0, this.p1, tmpDir);
|
||||
}
|
||||
}
|
||||
|
||||
if (isHelix !== isHelixNext1) {
|
||||
if (isHelix) {
|
||||
Vec3.copy(this.p4, this.p3);
|
||||
Vec3.copy(this.p5, this.p3);
|
||||
Vec3.copy(this.p6, this.p3);
|
||||
} else if(isHelixNext1) {
|
||||
Vec3.scale(tmpDir, Vec3.sub(tmpDir, this.p4, this.p3), 2);
|
||||
Vec3.add(this.p4, this.p3, tmpDir);
|
||||
Vec3.add(this.p5, this.p4, tmpDir);
|
||||
Vec3.add(this.p6, this.p5, tmpDir);
|
||||
}
|
||||
} else if (isHelix !== isHelixNext2) {
|
||||
if (isHelix) {
|
||||
Vec3.copy(this.p5, this.p4);
|
||||
Vec3.copy(this.p6, this.p4);
|
||||
} else if(isHelixNext2) {
|
||||
Vec3.scale(tmpDir, Vec3.sub(tmpDir, this.p5, this.p4), 2);
|
||||
Vec3.add(this.p5, this.p4, tmpDir);
|
||||
Vec3.add(this.p6, this.p5, tmpDir);
|
||||
}
|
||||
} else if (isHelix !== isHelixNext3) {
|
||||
if (isHelix) {
|
||||
Vec3.copy(this.p6, this.p5);
|
||||
} else if(isHelixNext3) {
|
||||
Vec3.scale(tmpDir, Vec3.sub(tmpDir, this.p6, this.p5), 2);
|
||||
Vec3.add(this.p6, this.p5, tmpDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.setFromToVector(this.d01, residueIndexPrev1, ssPrev1);
|
||||
this.setFromToVector(this.d12, residueIndex, ss);
|
||||
this.setFromToVector(this.d23, residueIndexNext1, ssNext1);
|
||||
this.setFromToVector(this.d34, residueIndexNext2, ssNext2);
|
||||
|
||||
const helixFlag = isHelix && this.helixOrientationCenters;
|
||||
|
||||
// extend termini
|
||||
const f = 0.5;
|
||||
if (residueIndex === residueIndexPrev1) {
|
||||
Vec3.scale(tmpDir, Vec3.sub(tmpDir, this.p3, this.p4), f);
|
||||
const f = 1.5;
|
||||
if (residueIndex === residueIndexPrev1 || (ss !== ssPrev1 && helixFlag)) {
|
||||
Vec3.setMagnitude(tmpDir, Vec3.sub(tmpDir, this.p3, this.p4), f);
|
||||
Vec3.add(this.p2, this.p3, tmpDir);
|
||||
Vec3.add(this.p1, this.p2, tmpDir);
|
||||
Vec3.add(this.p0, this.p1, tmpDir);
|
||||
} else if (residueIndexPrev1 === residueIndexPrev2) {
|
||||
Vec3.scale(tmpDir, Vec3.sub(tmpDir, this.p2, this.p3), f);
|
||||
} else if (residueIndexPrev1 === residueIndexPrev2 || (ss !== ssPrev2 && helixFlag)) {
|
||||
Vec3.setMagnitude(tmpDir, Vec3.sub(tmpDir, this.p2, this.p3), f);
|
||||
Vec3.add(this.p1, this.p2, tmpDir);
|
||||
Vec3.add(this.p0, this.p1, tmpDir);
|
||||
} else if (residueIndexPrev2 === residueIndexPrev3) {
|
||||
Vec3.scale(tmpDir, Vec3.sub(tmpDir, this.p1, this.p2), f);
|
||||
} else if (residueIndexPrev2 === residueIndexPrev3 || (ss !== ssPrev3 && helixFlag)) {
|
||||
Vec3.setMagnitude(tmpDir, Vec3.sub(tmpDir, this.p1, this.p2), f);
|
||||
Vec3.add(this.p0, this.p1, tmpDir);
|
||||
}
|
||||
if (residueIndex === residueIndexNext1) {
|
||||
Vec3.scale(tmpDir, Vec3.sub(tmpDir, this.p3, this.p2), f);
|
||||
if (residueIndex === residueIndexNext1 || (ss !== ssNext1 && helixFlag)) {
|
||||
Vec3.setMagnitude(tmpDir, Vec3.sub(tmpDir, this.p3, this.p2), f);
|
||||
Vec3.add(this.p4, this.p3, tmpDir);
|
||||
Vec3.add(this.p5, this.p4, tmpDir);
|
||||
Vec3.add(this.p6, this.p5, tmpDir);
|
||||
} else if (residueIndexNext1 === residueIndexNext2) {
|
||||
Vec3.scale(tmpDir, Vec3.sub(tmpDir, this.p4, this.p3), f);
|
||||
} else if (residueIndexNext1 === residueIndexNext2 || (ss !== ssNext2 && helixFlag)) {
|
||||
Vec3.setMagnitude(tmpDir, Vec3.sub(tmpDir, this.p4, this.p3), f);
|
||||
Vec3.add(this.p5, this.p4, tmpDir);
|
||||
Vec3.add(this.p6, this.p5, tmpDir);
|
||||
} else if (residueIndexNext2 === residueIndexNext3) {
|
||||
Vec3.scale(tmpDir, Vec3.sub(tmpDir, this.p5, this.p4), f);
|
||||
} else if (residueIndexNext2 === residueIndexNext3 || (ss !== ssNext3 && helixFlag)) {
|
||||
Vec3.setMagnitude(tmpDir, Vec3.sub(tmpDir, this.p5, this.p4), f);
|
||||
Vec3.add(this.p6, this.p5, tmpDir);
|
||||
}
|
||||
|
||||
this.setControlPoint(value.p0, this.p0, this.p1, this.p2, residueIndexPrev2);
|
||||
this.setControlPoint(value.p1, this.p1, this.p2, this.p3, residueIndexPrev1);
|
||||
this.setControlPoint(value.p2, this.p2, this.p3, this.p4, residueIndex);
|
||||
this.setControlPoint(value.p3, this.p3, this.p4, this.p5, residueIndexNext1);
|
||||
this.setControlPoint(value.p4, this.p4, this.p5, this.p6, residueIndexNext2);
|
||||
this.setControlPoint(value.p0, this.p0, this.p1, this.p2, ssPrev2);
|
||||
this.setControlPoint(value.p1, this.p1, this.p2, this.p3, ssPrev1);
|
||||
this.setControlPoint(value.p2, this.p2, this.p3, this.p4, ss);
|
||||
this.setControlPoint(value.p3, this.p3, this.p4, this.p5, ssNext1);
|
||||
this.setControlPoint(value.p4, this.p4, this.p5, this.p6, ssNext2);
|
||||
|
||||
this.setDirection(value.d12, this.d01, this.d12, this.d23);
|
||||
this.setDirection(value.d23, this.d12, this.d23, this.d34);
|
||||
@@ -284,7 +398,7 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
|
||||
return this.value;
|
||||
}
|
||||
|
||||
constructor(private unit: Unit.Atomic, structure: Structure, ignoreSecondaryStructure = false) {
|
||||
constructor(private unit: Unit.Atomic, structure: Structure, options: PolymerTraceIteratorOptions = {}) {
|
||||
this.atomicConformation = unit.model.atomicConformation;
|
||||
this.residueAtomSegments = unit.model.atomicHierarchy.residueAtomSegments;
|
||||
this.polymerRanges = unit.model.atomicRanges.polymerRanges;
|
||||
@@ -298,12 +412,15 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
|
||||
this.value = createPolymerTraceElement(structure, unit);
|
||||
this.hasNext = this.residueIt.hasNext && this.polymerIt.hasNext;
|
||||
|
||||
const secondaryStructure = !ignoreSecondaryStructure && SecondaryStructureProvider.get(structure).value?.get(unit.invariantId);
|
||||
if (secondaryStructure) {
|
||||
const { type, getIndex } = secondaryStructure;
|
||||
this.getSecStruc = (residueIndex: ResidueIndex) => type[getIndex(residueIndex as ResidueIndex)];
|
||||
} else {
|
||||
this.getSecStruc = (residueIndex: ResidueIndex) => SecStrucTypeNA;
|
||||
if (!options.ignoreSecondaryStructure) {
|
||||
this.secondaryStructure = SecondaryStructureProvider.get(structure).value?.get(unit.invariantId);
|
||||
}
|
||||
|
||||
if (options.useHelixOrientation) {
|
||||
const helixOrientation = HelixOrientationProvider.get(unit.model).value;
|
||||
if (!helixOrientation) throw new Error('missing helix-orientation');
|
||||
|
||||
this.helixOrientationCenters = helixOrientation.centers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user