Compare commits

...

15 Commits

Author SHA1 Message Date
dsehnal
8a76a3fa64 2.0.4 2021-04-20 11:23:23 +02:00
dsehnal
71bf4e21f5 changelog 2021-04-20 11:21:29 +02:00
dsehnal
e0d36c30d3 Fix measurement labels & interactions 2021-04-20 11:19:15 +02:00
David Sehnal
b53debcfef Merge pull request #163 from sukolsak/fix-zip-progress
Fix incorrect deflate progress
2021-04-14 15:02:15 +02:00
Sukolsak Sakshuwong
d0705ac226 fix deflate progress 2021-04-14 05:07:05 -07:00
Alexander Rose
e01eacb3fe changelog 2021-04-13 23:12:39 -07:00
Alexander Rose
d4102b476b Fix, read SDF multi-line values 2021-04-13 23:07:55 -07:00
dsehnal
83ce17174a changelog 2021-04-13 21:17:28 +02:00
dsehnal
18023d7f26 Merge branch 'master' of https://github.com/molstar/molstar 2021-04-13 21:16:28 +02:00
dsehnal
a8541d5967 Structure.eachAtomicHierarchyElement 2021-04-13 21:15:44 +02:00
David Sehnal
8b21818f2e Merge pull request #159 from sukolsak/obj-export
OBJ Export
2021-04-13 17:06:48 +02:00
Sukolsak Sakshuwong
0b290247dc show partial progress when exporting large meshes 2021-04-11 13:15:50 -07:00
Sukolsak Sakshuwong
fb5010e962 reorder arguments to addMeshWithColors() 2021-04-10 18:12:32 -07:00
Sukolsak Sakshuwong
178789d327 make RenderObjectExporter.add() async 2021-04-10 18:10:42 -07:00
Sukolsak Sakshuwong
4fae526073 wip 2021-04-09 15:59:14 -07:00
14 changed files with 811 additions and 40 deletions

View File

@@ -3,7 +3,12 @@ All notable changes to this project will be documented in this file, following t
Note that since we don't clearly distinguish between a public and private interfaces there will be changes in non-major versions that are potentially breaking. If we make breaking changes to less used interfaces we will highlight it in here.
## [Unreleased]
## [v2.0.4] - 2021-04-20
- [WIP] Mesh export extension
- ``Structure.eachAtomicHierarchyElement`` (#161)
- Fixed reading multi-line values in SDF format
- Fixed Measurements UI labels (#166)
## [v2.0.3] - 2021-04-09
### Added
@@ -13,8 +18,6 @@ Note that since we don't clearly distinguish between a public and private interf
- [Breaking] The `zip` function is now asynchronous and expects a `RuntimeContext`. Also added `Zip()` returning a `Task`.
- [Breaking] Add ``CubeGridFormat`` in ``alpha-orbitals`` extension.
## [v2.0.2] - 2021-03-29
### Added
- `Canvas3D.getRenderObjects`.

4
package-lock.json generated
View File

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

View File

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

View File

@@ -10,6 +10,7 @@ import { CellPack } from '../../extensions/cellpack';
import { DnatcoConfalPyramids } from '../../extensions/dnatco';
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
import { Mp4Export } from '../../extensions/mp4-export';
import { GeometryExport } from '../../extensions/geo-export';
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
@@ -55,7 +56,8 @@ const Extensions = {
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
'g3d': PluginSpec.Behavior(G3DFormat),
'mp4-export': PluginSpec.Behavior(Mp4Export)
'mp4-export': PluginSpec.Behavior(Mp4Export),
'geo-export': PluginSpec.Behavior(GeometryExport)
};
const DefaultViewerOptions = {

View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
import { PluginComponent } from '../../mol-plugin-state/component';
import { PluginContext } from '../../mol-plugin/context';
import { Task } from '../../mol-task';
import { ObjExporter } from './export';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { StateSelection } from '../../mol-state';
import { SetUtils } from '../../mol-util/set';
import { zip } from '../../mol-util/zip/zip';
export class GeometryControls extends PluginComponent {
getFilename() {
const models = this.plugin.state.data.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Model)).map(s => s.obj!.data);
const uniqueIds = new Set<string>();
models.forEach(m => uniqueIds.add(m.entryId.toUpperCase()));
const idString = SetUtils.toArray(uniqueIds).join('-');
return `${idString || 'molstar-model'}`;
}
exportObj() {
const task = Task.create('Export OBJ', async ctx => {
try {
const renderObjects = this.plugin.canvas3d?.getRenderObjects()!;
const filename = this.getFilename();
const objExporter = new ObjExporter(filename);
for (let i = 0, il = renderObjects.length; i < il; ++i) {
await ctx.update({ message: `Exporting object ${i}/${il}` });
await objExporter.add(renderObjects[i], ctx);
}
const { obj, mtl } = objExporter.getData();
const asciiWrite = (data: Uint8Array, str: string) => {
for (let i = 0, il = str.length; i < il; ++i) {
data[i] = str.charCodeAt(i);
}
};
const objData = new Uint8Array(obj.length);
asciiWrite(objData, obj);
const mtlData = new Uint8Array(mtl.length);
asciiWrite(mtlData, mtl);
const zipDataObj = {
[filename + '.obj']: objData,
[filename + '.mtl']: mtlData
};
const zipData = await zip(ctx, zipDataObj);
return {
zipData,
filename: filename + '.zip'
};
} catch (e) {
this.plugin.log.error('' + e);
throw e;
}
});
return this.plugin.runTask(task, { useOverlay: true });
}
constructor(private plugin: PluginContext) {
super();
}
}

View File

@@ -0,0 +1,321 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
import { GraphicsRenderObject } from '../../mol-gl/render-object';
import { MeshValues } from '../../mol-gl/renderable/mesh';
import { LinesValues } from '../../mol-gl/renderable/lines';
import { PointsValues } from '../../mol-gl/renderable/points';
import { SpheresValues } from '../../mol-gl/renderable/spheres';
import { CylindersValues } from '../../mol-gl/renderable/cylinders';
import { BaseValues, SizeValues } from '../../mol-gl/renderable/schema';
import { TextureImage } from '../../mol-gl/renderable/util';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
import { RuntimeContext } from '../../mol-task';
import { StringBuilder } from '../../mol-util';
import { Color } from '../../mol-util/color/color';
import { decodeFloatRGB } from '../../mol-util/float-packing';
// 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;
type RenderObjectExportData = {
[k: string]: string | Uint8Array | undefined
}
interface RenderObjectExporter<D extends RenderObjectExportData> {
add(renderObject: GraphicsRenderObject, ctx: RuntimeContext): Promise<void> | undefined
getData(): D
}
// http://paulbourke.net/dataformats/obj/
// http://paulbourke.net/dataformats/mtl/
export type ObjData = {
obj: string
mtl: string
}
export class ObjExporter implements RenderObjectExporter<ObjData> {
private obj = StringBuilder.create();
private mtl = StringBuilder.create();
private vertexOffset = 0;
private currentColor: Color | undefined;
private currentAlpha: number | undefined;
private materialSet = new Set<string>();
private static getSizeFromTexture(tSize: TextureImage<Uint8Array>, i: number): number {
const r = tSize.array[i * 3];
const g = tSize.array[i * 3 + 1];
const b = tSize.array[i * 3 + 2];
return decodeFloatRGB(r, g, b);
}
private static getSize(values: BaseValues & SizeValues, instanceIndex: number, group: number): number {
const tSize = values.tSize.ref.value;
let size = 0;
switch (values.dSizeType.ref.value) {
case 'uniform':
size = values.uSize.ref.value;
break;
case 'instance':
size = ObjExporter.getSizeFromTexture(tSize, instanceIndex) / 100;
break;
case 'group':
size = ObjExporter.getSizeFromTexture(tSize, group) / 100;
break;
case 'groupInstance':
const groupCount = values.uGroupCount.ref.value;
size = ObjExporter.getSizeFromTexture(tSize, instanceIndex * groupCount + group) / 100;
break;
}
return size * values.uSizeFactor.ref.value;
}
private updateMaterial(color: Color, alpha: number) {
if (this.currentColor === color && this.currentAlpha === alpha) return;
this.currentColor = color;
this.currentAlpha = alpha;
const material = Color.toHexString(color) + alpha;
StringBuilder.writeSafe(this.obj, `usemtl ${material}`);
StringBuilder.newline(this.obj);
if (!this.materialSet.has(material)) {
this.materialSet.add(material);
const [r, g, b] = Color.toRgbNormalized(color);
const mtl = this.mtl;
StringBuilder.writeSafe(mtl, `newmtl ${material}\n`);
StringBuilder.writeSafe(mtl, 'illum 2\n'); // illumination model
StringBuilder.writeSafe(mtl, 'Ns 163\n'); // specular exponent
StringBuilder.writeSafe(mtl, 'Ni 0.001\n'); // optical density a.k.a. index of refraction
StringBuilder.writeSafe(mtl, 'Ka 0 0 0\n'); // ambient reflectivity
StringBuilder.writeSafe(mtl, 'Kd '); // diffuse reflectivity
StringBuilder.writeFloat(mtl, r, 1000);
StringBuilder.whitespace1(mtl);
StringBuilder.writeFloat(mtl, g, 1000);
StringBuilder.whitespace1(mtl);
StringBuilder.writeFloat(mtl, b, 1000);
StringBuilder.newline(mtl);
StringBuilder.writeSafe(mtl, 'Ks 0.25 0.25 0.25\n'); // specular reflectivity
StringBuilder.writeSafe(mtl, 'd '); // dissolve
StringBuilder.writeFloat(mtl, alpha, 1000);
StringBuilder.newline(mtl);
}
}
private async addMeshWithColors(vertices: Float32Array, normals: Float32Array, indices: Uint32Array, groups: Float32Array, vertexCount: number, drawCount: number, values: BaseValues, instanceIndex: number, ctx: RuntimeContext) {
const obj = this.obj;
const t = Mat4();
const n = Mat3();
const tmpV = Vec3();
const colorType = values.dColorType.ref.value;
const tColor = values.tColor.ref.value.array;
const uAlpha = values.uAlpha.ref.value;
const aTransform = values.aTransform.ref.value;
Mat4.fromArray(t, aTransform, instanceIndex * 16);
mat3directionTransform(n, t);
const currentProgress = (vertexCount * 2 + drawCount) * instanceIndex;
await ctx.update({ isIndeterminate: false, current: currentProgress, max: (vertexCount * 2 + drawCount) * values.uInstanceCount.ref.value });
// position
for (let i = 0; i < vertexCount; ++i) {
if (i % 1000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + i });
v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * 3), t);
StringBuilder.writeSafe(obj, 'v ');
StringBuilder.writeFloat(obj, tmpV[0], 1000);
StringBuilder.whitespace1(obj);
StringBuilder.writeFloat(obj, tmpV[1], 1000);
StringBuilder.whitespace1(obj);
StringBuilder.writeFloat(obj, tmpV[2], 1000);
StringBuilder.newline(obj);
}
// normal
for (let i = 0; i < vertexCount; ++i) {
if (i % 1000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + vertexCount + i });
v3transformMat3(tmpV, v3fromArray(tmpV, normals, i * 3), n);
StringBuilder.writeSafe(obj, 'vn ');
StringBuilder.writeFloat(obj, tmpV[0], 100);
StringBuilder.whitespace1(obj);
StringBuilder.writeFloat(obj, tmpV[1], 100);
StringBuilder.whitespace1(obj);
StringBuilder.writeFloat(obj, tmpV[2], 100);
StringBuilder.newline(obj);
}
// face
for (let i = 0; i < drawCount; i += 3) {
if (i % 3000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + vertexCount * 2 + i });
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':
color = Color.fromArray(tColor, groups[indices[i]] * 3);
break;
case 'groupInstance':
const groupCount = values.uGroupCount.ref.value;
const group = groups[indices[i]];
color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
break;
case 'vertex':
color = Color.fromArray(tColor, i * 3);
break;
case 'vertexInstance':
color = Color.fromArray(tColor, (instanceIndex * drawCount + i) * 3);
break;
default: throw new Error('Unsupported color type.');
}
this.updateMaterial(color, uAlpha);
const v1 = this.vertexOffset + indices[i] + 1;
const v2 = this.vertexOffset + indices[i + 1] + 1;
const v3 = this.vertexOffset + indices[i + 2] + 1;
StringBuilder.writeSafe(obj, 'f ');
StringBuilder.writeInteger(obj, v1);
StringBuilder.writeSafe(obj, '//');
StringBuilder.writeIntegerAndSpace(obj, v1);
StringBuilder.writeInteger(obj, v2);
StringBuilder.writeSafe(obj, '//');
StringBuilder.writeIntegerAndSpace(obj, v2);
StringBuilder.writeInteger(obj, v3);
StringBuilder.writeSafe(obj, '//');
StringBuilder.writeInteger(obj, v3);
StringBuilder.newline(obj);
}
this.vertexOffset += vertexCount;
}
private async addMesh(values: MeshValues, ctx: RuntimeContext) {
const aPosition = values.aPosition.ref.value;
const aNormal = values.aNormal.ref.value;
const elements = values.elements.ref.value;
const aGroup = values.aGroup.ref.value;
const instanceCount = values.instanceCount.ref.value;
const vertexCount = values.uVertexCount.ref.value;
const drawCount = values.drawCount.ref.value;
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
await this.addMeshWithColors(aPosition, aNormal, elements, aGroup, vertexCount, drawCount, values, instanceIndex, ctx);
}
}
private async addLines(values: LinesValues, ctx: RuntimeContext) {
// TODO
}
private async addPoints(values: PointsValues, ctx: RuntimeContext) {
// TODO
}
private async addSpheres(values: SpheresValues, ctx: RuntimeContext) {
const center = Vec3();
const aPosition = values.aPosition.ref.value;
const aGroup = values.aGroup.ref.value;
const instanceCount = values.instanceCount.ref.value;
const vertexCount = values.uVertexCount.ref.value;
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
const state = MeshBuilder.createState(512, 256);
for (let i = 0; i < vertexCount; i += 4) {
v3fromArray(center, aPosition, i * 3);
const group = aGroup[i];
const radius = ObjExporter.getSize(values, instanceIndex, group);
state.currentGroup = group;
addSphere(state, center, radius, 2);
}
const mesh = MeshBuilder.getMesh(state);
const vertices = mesh.vertexBuffer.ref.value;
const normals = mesh.normalBuffer.ref.value;
const indices = mesh.indexBuffer.ref.value;
const groups = mesh.groupBuffer.ref.value;
await this.addMeshWithColors(vertices, normals, indices, groups, vertices.length / 3, indices.length, values, instanceIndex, ctx);
}
}
private async addCylinders(values: CylindersValues, ctx: RuntimeContext) {
const start = Vec3();
const end = Vec3();
const aStart = values.aStart.ref.value;
const aEnd = values.aEnd.ref.value;
const aScale = values.aScale.ref.value;
const aCap = values.aCap.ref.value;
const aGroup = values.aGroup.ref.value;
const instanceCount = values.instanceCount.ref.value;
const vertexCount = values.uVertexCount.ref.value;
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
const state = MeshBuilder.createState(512, 256);
for (let i = 0; i < vertexCount; i += 6) {
v3fromArray(start, aStart, i * 3);
v3fromArray(end, aEnd, i * 3);
const group = aGroup[i];
const radius = ObjExporter.getSize(values, instanceIndex, group) * aScale[i];
const cap = aCap[i];
const topCap = cap === 1 || cap === 3;
const bottomCap = cap >= 2;
const cylinderProps = { radiusTop: radius, radiusBottom: radius, topCap, bottomCap, radialSegments: 32 };
state.currentGroup = aGroup[i];
addCylinder(state, start, end, 1, cylinderProps);
}
const mesh = MeshBuilder.getMesh(state);
const vertices = mesh.vertexBuffer.ref.value;
const normals = mesh.normalBuffer.ref.value;
const indices = mesh.indexBuffer.ref.value;
const groups = mesh.groupBuffer.ref.value;
await this.addMeshWithColors(vertices, normals, indices, groups, vertices.length / 3, indices.length, values, instanceIndex, ctx);
}
}
add(renderObject: GraphicsRenderObject, ctx: RuntimeContext) {
if (!renderObject.state.visible) return;
switch (renderObject.type) {
case 'mesh':
return this.addMesh(renderObject.values as MeshValues, ctx);
case 'lines':
return this.addLines(renderObject.values as LinesValues, ctx);
case 'points':
return this.addPoints(renderObject.values as PointsValues, ctx);
case 'spheres':
return this.addSpheres(renderObject.values as SpheresValues, ctx);
case 'cylinders':
return this.addCylinders(renderObject.values as CylindersValues, ctx);
}
}
getData() {
return {
obj: StringBuilder.getString(this.obj),
mtl: StringBuilder.getString(this.mtl)
};
}
constructor(filename: string) {
StringBuilder.writeSafe(this.obj, `mtllib ${filename}.mtl\n`);
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
import { PluginBehavior } from '../../mol-plugin/behavior/behavior';
import { GeometryExporterUI } from './ui';
export const GeometryExport = PluginBehavior.create<{ }>({
name: 'extension-geo-export',
category: 'misc',
display: {
name: 'Geometry Export'
},
ctor: class extends PluginBehavior.Handler<{ }> {
register(): void {
this.ctx.customStructureControls.set('geo-export', GeometryExporterUI as any);
}
update() {
return false;
}
unregister() {
this.ctx.customStructureControls.delete('geo-export');
}
},
params: () => ({ })
});

View File

@@ -0,0 +1,64 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
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 { download } from '../../mol-util/download';
import { GeometryControls } from './controls';
interface State {
busy?: boolean
}
export class GeometryExporterUI extends CollapsableControls<{}, State> {
private _controls: GeometryControls | undefined;
get controls() {
return this._controls || (this._controls = new GeometryControls(this.plugin));
}
protected defaultState(): State & CollapsableState {
return {
header: 'Export Geometries',
isCollapsed: true,
brand: { accent: 'cyan', svg: CubeSendSvg }
};
}
protected renderControls(): JSX.Element {
return <>
<Button icon={GetAppSvg}
onClick={this.saveObj} style={{ marginTop: 1 }}
disabled={this.state.busy || !this.plugin.canvas3d?.reprCount.value}>
Save OBJ + MTL
</Button>
</>;
}
componentDidMount() {
this.subscribe(this.plugin.canvas3d!.reprCount, () => {
if (!this.state.isCollapsed) this.forceUpdate();
});
}
componentWillUnmount() {
this._controls?.dispose();
this._controls = void 0;
}
saveObj = async () => {
try {
this.setState({ busy: true });
const data = await this.controls.exportObj();
this.setState({ busy: false });
download(new Blob([data.zipData]), data.filename);
} catch {
this.setState({ busy: false });
}
}
}

View File

@@ -2,7 +2,7 @@
import { parseSdf } from '../sdf/parser';
const SdfString = `
Mrv1718007121815122D
Mrv1718007121815122D
5 4 0 0 0 0 999 V2000
0.0000 0.8250 0.0000 O 0 5 0 0 0 0 0 0 0 0 0 0
@@ -129,7 +129,208 @@ Comp 2
M CHG 3 1 -1 3 -1 5 -1
M END
> <DATABASE_ID>
1`;
1
$$$$
2244
-OEChem-04122119123D
21 21 0 0 0 0 0 0 0999 V2000
1.2333 0.5540 0.7792 O 0 0 0 0 0 0 0 0 0 0 0 0
-0.6952 -2.7148 -0.7502 O 0 0 0 0 0 0 0 0 0 0 0 0
0.7958 -2.1843 0.8685 O 0 0 0 0 0 0 0 0 0 0 0 0
1.7813 0.8105 -1.4821 O 0 0 0 0 0 0 0 0 0 0 0 0
-0.0857 0.6088 0.4403 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.7927 -0.5515 0.1244 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.7288 1.8464 0.4133 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.1426 -0.4741 -0.2184 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.0787 1.9238 0.0706 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.7855 0.7636 -0.2453 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.1409 -1.8536 0.1477 C 0 0 0 0 0 0 0 0 0 0 0 0
2.1094 0.6715 -0.3113 C 0 0 0 0 0 0 0 0 0 0 0 0
3.5305 0.5996 0.1635 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.1851 2.7545 0.6593 H 0 0 0 0 0 0 0 0 0 0 0 0
-2.7247 -1.3605 -0.4564 H 0 0 0 0 0 0 0 0 0 0 0 0
-2.5797 2.8872 0.0506 H 0 0 0 0 0 0 0 0 0 0 0 0
-3.8374 0.8238 -0.5090 H 0 0 0 0 0 0 0 0 0 0 0 0
3.7290 1.4184 0.8593 H 0 0 0 0 0 0 0 0 0 0 0 0
4.2045 0.6969 -0.6924 H 0 0 0 0 0 0 0 0 0 0 0 0
3.7105 -0.3659 0.6426 H 0 0 0 0 0 0 0 0 0 0 0 0
-0.2555 -3.5916 -0.7337 H 0 0 0 0 0 0 0 0 0 0 0 0
1 5 1 0 0 0 0
1 12 1 0 0 0 0
2 11 1 0 0 0 0
2 21 1 0 0 0 0
3 11 2 0 0 0 0
4 12 2 0 0 0 0
5 6 1 0 0 0 0
5 7 2 0 0 0 0
6 8 2 0 0 0 0
6 11 1 0 0 0 0
7 9 1 0 0 0 0
7 14 1 0 0 0 0
8 10 1 0 0 0 0
8 15 1 0 0 0 0
9 10 2 0 0 0 0
9 16 1 0 0 0 0
10 17 1 0 0 0 0
12 13 1 0 0 0 0
13 18 1 0 0 0 0
13 19 1 0 0 0 0
13 20 1 0 0 0 0
M END
> <PUBCHEM_COMPOUND_CID>
2244
> <PUBCHEM_CONFORMER_RMSD>
0.6
> <PUBCHEM_CONFORMER_DIVERSEORDER>
1
11
10
3
15
17
13
5
16
7
14
9
8
4
18
6
12
2
> <PUBCHEM_MMFF94_PARTIAL_CHARGES>
18
1 -0.23
10 -0.15
11 0.63
12 0.66
13 0.06
14 0.15
15 0.15
16 0.15
17 0.15
2 -0.65
21 0.5
3 -0.57
4 -0.57
5 0.08
6 0.09
7 -0.15
8 -0.15
9 -0.15
> <PUBCHEM_EFFECTIVE_ROTOR_COUNT>
3
> <PUBCHEM_PHARMACOPHORE_FEATURES>
5
1 2 acceptor
1 3 acceptor
1 4 acceptor
3 2 3 11 anion
6 5 6 7 8 9 10 rings
> <PUBCHEM_HEAVY_ATOM_COUNT>
13
> <PUBCHEM_ATOM_DEF_STEREO_COUNT>
0
> <PUBCHEM_ATOM_UDEF_STEREO_COUNT>
0
> <PUBCHEM_BOND_DEF_STEREO_COUNT>
0
> <PUBCHEM_BOND_UDEF_STEREO_COUNT>
0
> <PUBCHEM_ISOTOPIC_ATOM_COUNT>
0
> <PUBCHEM_COMPONENT_COUNT>
1
> <PUBCHEM_CACTVS_TAUTO_COUNT>
1
> <PUBCHEM_CONFORMER_ID>
000008C400000001
> <PUBCHEM_MMFF94_ENERGY>
39.5952
> <PUBCHEM_FEATURE_SELFOVERLAP>
25.432
> <PUBCHEM_SHAPE_FINGERPRINT>
1 1 18265615372930943622
100427 49 16967750034970055351
12138202 97 18271247217817981012
12423570 1 16692715976000295083
12524768 44 16753525617747228747
12716758 59 18341332292274886536
13024252 1 17968377969333732145
14181834 199 17830728755827362645
14614273 12 18262232214645093005
15207287 21 17703787037639964108
15775835 57 18340488876329928641
16945 1 18271533103414939405
193761 8 17907860604865584321
20645476 183 17677348215414174190
20871998 184 18198632231250704846
21040471 1 18411412921197846465
21501502 16 18123463883164380929
23402539 116 18271795865171824860
23419403 2 13539898140662769886
23552423 10 18048876295495619569
23559900 14 18272369794190581304
241688 4 16179044415907240795
257057 1 17478316999871287486
2748010 2 18339085878070479087
305870 269 18263645056784260212
528862 383 18117272558388284091
53812653 8 18410289211719108569
7364860 26 17910392788380644719
81228 2 18050568744116491203
> <PUBCHEM_SHAPE_MULTIPOLES>
244.06
3.86
2.45
0.89
1.95
1.58
0.15
-1.85
0.38
-0.61
-0.02
0.29
0.01
-0.33
> <PUBCHEM_SHAPE_SELFOVERLAP>
513.037
> <PUBCHEM_SHAPE_VOLUME>
136
> <PUBCHEM_COORDINATE_TYPE>
2
5
10
$$$$
`;
describe('sdf reader', () => {
it('basic', async () => {
@@ -139,10 +340,11 @@ describe('sdf reader', () => {
}
const compound1 = parsed.result.compounds[0];
const compound2 = parsed.result.compounds[1];
const compound3 = parsed.result.compounds[2];
const { molFile, dataItems } = compound1;
const { atoms, bonds } = molFile;
expect(parsed.result.compounds.length).toBe(2);
expect(parsed.result.compounds.length).toBe(3);
// number of structures
expect(atoms.count).toBe(5);
@@ -171,5 +373,11 @@ describe('sdf reader', () => {
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.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.data.value(21)).toBe('2\n5\n10');
});
});

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Column } from '../../../mol-data/db';
@@ -27,21 +28,31 @@ function handleDataItems(tokenizer: Tokenizer): { dataHeader: Column<string>, da
const dataHeader = TokenBuilder.create(tokenizer.data, 32);
const data = TokenBuilder.create(tokenizer.data, 32);
let sawHeaderToken = false;
while (tokenizer.position < tokenizer.length) {
const line = Tokenizer.readLine(tokenizer);
if (line.startsWith(delimiter)) break;
if (!!line) {
if (line.startsWith('> <')) {
TokenBuilder.add(dataHeader, tokenizer.tokenStart + 3, tokenizer.tokenEnd - 1);
sawHeaderToken = true;
} else if (sawHeaderToken) {
TokenBuilder.add(data, tokenizer.tokenStart, tokenizer.tokenEnd);
sawHeaderToken = false;
// TODO can there be multiline values?
if (!line) continue;
if (line.startsWith('> <')) {
TokenBuilder.add(dataHeader, tokenizer.tokenStart + 3, tokenizer.tokenEnd - 1);
Tokenizer.markLine(tokenizer);
const start = tokenizer.tokenStart;
let end = tokenizer.tokenEnd;
let added = false;
while (tokenizer.position < tokenizer.length) {
const line2 = Tokenizer.readLine(tokenizer);
if (!line2 || line2.startsWith(delimiter) || line2.startsWith('> <')) {
TokenBuilder.add(data, start, end);
added = true;
break;
}
end = tokenizer.tokenEnd;
}
if (!added) {
TokenBuilder.add(data, start, end);
}
} else {
sawHeaderToken = false;
}
}

View File

@@ -1167,6 +1167,57 @@ namespace Structure {
}
}
export interface ForEachAtomicHierarchyElementParams {
// Called for 1st element of each chain
// Note that chains can be split, meaning each chain would be called multiple times.
chain?: (e: StructureElement.Location<Unit.Atomic>) => void,
// Called for 1st element of each residue
residue?: (e: StructureElement.Location<Unit.Atomic>) => void,
// Called for each element
atom?: (e: StructureElement.Location<Unit.Atomic>) => void,
};
export function eachAtomicHierarchyElement(structure: Structure, { chain, residue, atom }: ForEachAtomicHierarchyElementParams) {
const l = StructureElement.Location.create<Unit.Atomic>(structure);
for (const unit of structure.units) {
if (unit.kind !== Unit.Kind.Atomic) continue;
l.unit = unit;
const { elements } = unit;
const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
while (chainsIt.hasNext) {
const chainSegment = chainsIt.move();
if (chain) {
l.element = elements[chainSegment.start];
chain(l);
}
if (!residue && !atom) continue;
residuesIt.setSegment(chainSegment);
while (residuesIt.hasNext) {
const residueSegment = residuesIt.move();
if (residue) {
l.element = elements[residueSegment.start];
residue(l);
}
if (!atom) continue;
for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) {
l.element = elements[j];
atom(l);
}
}
}
}
}
//
export const DefaultSizeThresholds = {

View File

@@ -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 _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; }
const _CursorDefaultOutline = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M10.07,14.27C10.57,14.03 11.16,14.25 11.4,14.75L13.7,19.74L15.5,18.89L13.19,13.91C12.95,13.41 13.17,12.81 13.67,12.58L13.95,12.5L16.25,12.05L8,5.12V15.9L9.82,14.43L10.07,14.27M13.64,21.97C13.14,22.21 12.54,22 12.31,21.5L10.13,16.76L7.62,18.78C7.45,18.92 7.24,19 7,19A1,1 0 0,1 6,18V3A1,1 0 0,1 7,2C7.24,2 7.47,2.09 7.64,2.23L7.65,2.22L19.14,11.86C19.57,12.22 19.62,12.85 19.27,13.27C19.12,13.45 18.91,13.57 18.7,13.61L15.54,14.23L17.74,18.96C18,19.46 17.76,20.05 17.26,20.28L13.64,21.97Z' /></svg>;
export function CursorDefaultOutlineSvg() { return _CursorDefaultOutline; }

View File

@@ -10,14 +10,17 @@ import { Loci } from '../../mol-model/loci';
import { StructureElement } from '../../mol-model/structure';
import { StructureMeasurementCell, StructureMeasurementOptions, StructureMeasurementParams } from '../../mol-plugin-state/manager/structure/measurement';
import { StructureSelectionHistoryEntry } from '../../mol-plugin-state/manager/structure/selection';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { PluginCommands } from '../../mol-plugin/commands';
import { AngleData } from '../../mol-repr/shape/loci/angle';
import { DihedralData } from '../../mol-repr/shape/loci/dihedral';
import { DistanceData } from '../../mol-repr/shape/loci/distance';
import { LabelData } from '../../mol-repr/shape/loci/label';
import { angleLabel, dihedralLabel, distanceLabel, lociLabel } from '../../mol-theme/label';
import { FiniteArray } from '../../mol-util/type-helpers';
import { CollapsableControls, PurePluginUIComponent } from '../base';
import { ActionMenu } from '../controls/action-menu';
import { Button, ExpandGroup, IconButton, ToggleButton } from '../controls/common';
import { Icon, PencilRulerSvg, SetSvg, ArrowUpwardSvg, ArrowDownwardSvg, DeleteOutlinedSvg, HelpOutlineSvg, AddSvg, TuneSvg, VisibilityOffOutlinedSvg, VisibilityOutlinedSvg, MoreHorizSvg } from '../controls/icons';
import { AddSvg, ArrowDownwardSvg, ArrowUpwardSvg, DeleteOutlinedSvg, HelpOutlineSvg, Icon, MoreHorizSvg, PencilRulerSvg, SetSvg, TuneSvg, VisibilityOffOutlinedSvg, VisibilityOutlinedSvg } from '../controls/icons';
import { ParameterControls } from '../controls/parameters';
import { UpdateTransformControl } from '../state/update-transform';
import { ToggleSelectionModeButton } from './selection';
@@ -216,7 +219,7 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
}
get selections() {
return this.props.cell.obj?.data.sourceData as ReadonlyArray<PluginStateObject.Molecule.Structure.SelectionEntry> | undefined;
return this.props.cell.obj?.data.sourceData as Partial<DistanceData & AngleData & DihedralData & LabelData> | undefined;
}
delete = () => {
@@ -234,8 +237,8 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
if (!selections) return;
this.plugin.managers.interactivity.lociHighlights.clearHighlights();
for (const d of selections) {
this.plugin.managers.interactivity.lociHighlights.highlight({ loci: d.loci }, false);
for (const loci of this.lociArray) {
this.plugin.managers.interactivity.lociHighlights.highlight({ loci }, false);
}
this.plugin.managers.interactivity.lociHighlights.highlight({ loci: this.props.cell.obj?.data.repr.getLoci()! }, false);
}
@@ -250,21 +253,31 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
const selections = this.selections;
if (!selections) return;
const sphere = Loci.getBundleBoundingSphere(toLociBundle(selections));
const sphere = Loci.getBundleBoundingSphere({ loci: this.lociArray });
if (sphere) {
this.plugin.managers.camera.focusSphere(sphere);
}
}
private get lociArray(): FiniteArray<Loci> {
const selections = this.selections;
if (!selections) return [];
if (selections.infos) return [selections.infos[0].loci];
if (selections.pairs) return selections.pairs[0].loci;
if (selections.triples) return selections.triples[0].loci;
if (selections.quads) return selections.quads[0].loci;
return [];
}
get label() {
const selections = this.selections;
switch (selections?.length) {
case 1: return lociLabel(selections[0].loci, { condensed: true });
case 2: return distanceLabel(toLociBundle(selections), { condensed: true, unitLabel: this.plugin.managers.structure.measurement.state.options.distanceUnitLabel });
case 3: return angleLabel(toLociBundle(selections), { condensed: true });
case 4: return dihedralLabel(toLociBundle(selections), { condensed: true });
default: return '';
}
if (!selections) return '<empty>';
if (selections.infos) return lociLabel(selections.infos[0].loci, { condensed: true });
if (selections.pairs) return distanceLabel(selections.pairs[0], { condensed: true, unitLabel: this.plugin.managers.structure.measurement.state.options.distanceUnitLabel });
if (selections.triples) return angleLabel(selections.triples[0], { condensed: true });
if (selections.quads) return dihedralLabel(selections.quads[0], { condensed: true });
return '<empty>';
}
get actions(): ActionMenu.Items {
@@ -302,8 +315,4 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
</>}
</>;
}
}
function toLociBundle(data: FiniteArray<{ loci: Loci }, any>): { loci: FiniteArray<Loci, any> } {
return { loci: (data.map(d => d.loci) as unknown as FiniteArray<Loci, any>) };
}

View File

@@ -139,7 +139,7 @@ export async function _deflateRaw(runtime: RuntimeContext, data: Uint8Array, out
while (ctx.i < dlen) {
if (runtime.shouldUpdate) {
await runtime.update({ message: 'Deflating...', current: ctx.pos, max: data.length });
await runtime.update({ message: 'Deflating...', current: ctx.i, max: dlen });
}
deflateChunk(ctx, 1024 * 1024);
}