Compare commits

...

17 Commits

Author SHA1 Message Date
dsehnal
166d660fa7 2.0.0-dev.10 2021-03-15 20:20:21 +01:00
dsehnal
b8249cde4d interactive camera axis helper 2021-03-15 20:16:07 +01:00
dsehnal
cd3798b46f disable SwaggerUI response syntax highlight 2021-03-15 16:44:36 +01:00
dsehnal
0240e54737 TrackballControlsParams.autoAdjustControls 2021-03-15 14:13:11 +01:00
dsehnal
6a735d902e fix XYZ parser bug 2021-03-15 13:31:44 +01:00
dsehnal
57a942ecb5 requestCameraReset SnapshotProvide
- allow to customize the snapshop based on the current scane/boundingbox/camera state
2021-03-15 12:47:45 +01:00
dsehnal
f67605a398 applyMarkerAction fix 2 2021-03-14 18:46:04 +01:00
dsehnal
864befc48a applyMarkerAction fix 2021-03-14 13:09:53 +01:00
Alexander Rose
78c0471f39 remove unused Structure.unitsSortedByVolume 2021-03-13 22:35:24 -08:00
Alexander Rose
c57b9b9214 improve preset for many polymer gaps
- show all atom instead
- for medium sized structures
- fixes #57
2021-03-13 22:27:51 -08:00
Alexander Rose
34f33c5bbb fix apply marker type error 2021-03-13 22:24:48 -08:00
Alexander Rose
57da2a7ebb optimized applyMarkerAction
- extract switch statement out of loop
- use int32 view to handle 4 byte together
- don't check for change (essentially done at a higher level anyway)
2021-03-13 12:22:53 -08:00
Alexander Rose
d45d5c0e55 add assertUnreachable helper
- to type check if, e.g. if/switch statements are exhaustive
- TODO use...
2021-03-13 12:20:00 -08:00
dsehnal
42ed425e65 fix secondary_structure_type 2021-03-13 19:58:50 +01:00
dsehnal
f752ee5094 plugin-state server: remove /clear and can't remotely remove sticky entries 2021-03-13 17:56:18 +01:00
dsehnal
044c796942 Fix getSymmetryOperatorRef indexing 2021-03-13 17:53:42 +01:00
dsehnal
0aabbcfaab add back CreateVolumeStreamingBehavior custom controls 2021-03-13 16:40:48 +01:00
24 changed files with 390 additions and 145 deletions

4
package-lock.json generated
View File

@@ -1,11 +1,11 @@
{
"name": "molstar",
"version": "2.0.0-dev.9",
"version": "2.0.0-dev.10",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "2.0.0-dev.8",
"version": "2.0.0-dev.10",
"license": "MIT",
"dependencies": {
"@types/argparse": "^1.0.38",

View File

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

View File

@@ -9,6 +9,7 @@ import { Mat4, Vec3, Vec4, EPSILON } from '../mol-math/linear-algebra';
import { Viewport, cameraProject, cameraUnproject } from './camera/util';
import { CameraTransitionManager } from './camera/transition';
import { BehaviorSubject } from 'rxjs';
import { Scene } from '../mol-gl/scene';
export { ICamera, Camera };
@@ -126,6 +127,23 @@ class Camera implements ICamera {
return state;
}
getInvariantFocus(target: Vec3, radius: number, up: Vec3, dir: Vec3): Partial<Camera.Snapshot> {
const r = Math.max(radius, 0.01);
const targetDistance = this.getTargetDistance(r);
Vec3.copy(this.deltaDirection, dir);
Vec3.setMagnitude(this.deltaDirection, this.deltaDirection, targetDistance);
Vec3.sub(this.newPosition, target, this.deltaDirection);
const state = Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state);
state.target = Vec3.clone(target);
state.radius = r;
state.position = Vec3.clone(this.newPosition);
Vec3.copy(state.up, up);
return state;
}
focus(target: Vec3, radius: number, durationMs?: number, up?: Vec3, dir?: Vec3) {
if (radius > 0) {
this.setState(this.getFocus(target, radius, up, dir), durationMs);
@@ -150,6 +168,8 @@ class Camera implements ICamera {
namespace Camera {
export type Mode = 'perspective' | 'orthographic'
export type SnapshotProvider = Partial<Snapshot> | ((scene: Scene, camera: Camera) => Partial<Snapshot>)
/**
* Sets an offseted view in a larger frustum. This is useful for
* - multi-window or multi-monitor/multi-machine setups

View File

@@ -229,7 +229,7 @@ interface Canvas3D {
/** performs handleResize on the next animation frame */
requestResize(): void
/** Focuses camera on scene's bounding sphere, centered and zoomed. */
requestCameraReset(options?: { durationMs?: number, snapshot?: Partial<Camera.Snapshot> }): void
requestCameraReset(options?: { durationMs?: number, snapshot?: Camera.SnapshotProvider }): void
readonly camera: Camera
readonly boundingSphere: Readonly<Sphere3D>
setProps(props: PartialCanvas3DProps | ((old: Canvas3DProps) => Partial<Canvas3DProps> | void), doNotRequestDraw?: boolean /* = false */): void
@@ -296,7 +296,7 @@ namespace Canvas3D {
let drawPending = false;
let cameraResetRequested = false;
let nextCameraResetDuration: number | undefined = void 0;
let nextCameraResetSnapshot: Partial<Camera.Snapshot> | undefined = void 0;
let nextCameraResetSnapshot: Camera.SnapshotProvider | undefined = void 0;
let resizeRequested = false;
let notifyDidDraw = true;
@@ -305,7 +305,11 @@ namespace Canvas3D {
let loci: Loci = EmptyLoci;
let repr: Representation.Any = Representation.Empty;
if (pickingId) {
const cameraHelperLoci = helper.camera.getLoci(pickingId);
if (cameraHelperLoci !== EmptyLoci) return { loci: cameraHelperLoci, repr };
loci = helper.handle.getLoci(pickingId);
reprRenderObjects.forEach((_, _repr) => {
const _loci = _repr.getLoci(pickingId);
if (!isEmptyLoci(_loci)) {
@@ -453,11 +457,21 @@ namespace Canvas3D {
function resolveCameraReset() {
if (!cameraResetRequested) return;
const { center, radius } = scene.boundingSphereVisible;
const boundingSphere = scene.boundingSphereVisible;
const { center, radius } = boundingSphere;
const autoAdjustControls = controls.props.autoAdjustMinMaxDistance;
if (autoAdjustControls.name === 'on') {
const minDistance = autoAdjustControls.params.minDistanceFactor * radius + autoAdjustControls.params.minDistancePadding;
const maxDistance = Math.max(autoAdjustControls.params.maxDistanceFactor * radius, autoAdjustControls.params.maxDistanceMin);
controls.setProps({ minDistance, maxDistance });
}
if (radius > 0) {
const duration = nextCameraResetDuration === undefined ? p.cameraResetDurationMs : nextCameraResetDuration;
const focus = camera.getFocus(center, radius);
const snapshot = nextCameraResetSnapshot ? { ...focus, ...nextCameraResetSnapshot } : focus;
const next = typeof nextCameraResetSnapshot === 'function' ? nextCameraResetSnapshot(scene, camera) : nextCameraResetSnapshot;
const snapshot = next ? { ...focus, ...next } : focus;
camera.setState({ ...snapshot, radiusMax: scene.boundingSphere.radius }, duration);
}

View File

@@ -49,7 +49,21 @@ export const TrackballControlsParams = {
minDistance: PD.Numeric(0.01, {}, { isHidden: true }),
maxDistance: PD.Numeric(1e150, {}, { isHidden: true }),
bindings: PD.Value(DefaultTrackballBindings, { isHidden: true })
bindings: PD.Value(DefaultTrackballBindings, { isHidden: true }),
/**
* minDistance = minDistanceFactor * boundingSphere.radius + minDistancePadding
* maxDistance = max(maxDistanceFactor * boundingSphere.radius, maxDistanceMin)
*/
autoAdjustMinMaxDistance: PD.MappedStatic('on', {
off: PD.EmptyGroup(),
on: PD.Group({
minDistanceFactor: PD.Numeric(0),
minDistancePadding: PD.Numeric(5),
maxDistanceFactor: PD.Numeric(10),
maxDistanceMin: PD.Numeric(20)
})
}, { isHidden: true })
};
export type TrackballControlsProps = PD.Values<typeof TrackballControlsParams>

View File

@@ -4,27 +4,29 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Scene } from '../../mol-gl/scene';
import { Camera, ICamera } from '../camera';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
import { GraphicsRenderObject } from '../../mol-gl/render-object';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { ColorNames } from '../../mol-util/color/names';
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
import { Viewport } from '../camera/util';
import { Sphere3D } from '../../mol-math/geometry';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import produce from 'immer';
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { PickingId } from '../../mol-geo/geometry/picking';
import { GraphicsRenderObject } from '../../mol-gl/render-object';
import { Scene } from '../../mol-gl/scene';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Sphere3D } from '../../mol-math/geometry';
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
import { DataLoci, EmptyLoci, Loci } from '../../mol-model/loci';
import { Shape } from '../../mol-model/shape';
import { ColorNames } from '../../mol-util/color/names';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Camera, ICamera } from '../camera';
import { Viewport } from '../camera/util';
// TODO add scale line/grid
const AxesParams = {
...Mesh.Params,
alpha: { ...Mesh.Params.alpha, defaultValue: 0.33 },
alpha: { ...Mesh.Params.alpha, defaultValue: 0.51 },
ignoreLight: { ...Mesh.Params.ignoreLight, defaultValue: true },
colorX: PD.Color(ColorNames.red, { isEssential: true }),
colorY: PD.Color(ColorNames.green, { isEssential: true }),
@@ -87,6 +89,12 @@ export class CameraHelper {
return this.props.axes.name === 'on';
}
getLoci(pickingId: PickingId) {
const { objectId, groupId, instanceId } = pickingId;
if (!this.renderObject || objectId !== this.renderObject.id || groupId === CameraHelperAxis.None) return EmptyLoci;
return CameraAxesLoci(this, groupId, instanceId);
}
update(camera: ICamera) {
if (!this.renderObject) return;
@@ -102,6 +110,38 @@ export class CameraHelper {
}
}
export const enum CameraHelperAxis {
None = 0,
X,
Y,
Z,
XY,
XZ,
YZ
}
function getAxisLabel(axis: number) {
switch (axis) {
case CameraHelperAxis.X: return 'X Axis';
case CameraHelperAxis.Y: return 'Y Axis';
case CameraHelperAxis.Z: return 'Z Axis';
case CameraHelperAxis.XY: return 'XY Plane';
case CameraHelperAxis.XZ: return 'XZ Plane';
case CameraHelperAxis.YZ: return 'YZ Plane';
default: return 'Axes';
}
}
function CameraAxesLoci(cameraHelper: CameraHelper, groupId: number, instanceId: number) {
return DataLoci('camera-axes', cameraHelper, [{ groupId, instanceId }],
void 0 /** bounding sphere */,
() => getAxisLabel(groupId));
}
export type CameraAxesLoci = ReturnType<typeof CameraAxesLoci>
export function isCameraAxesLoci(x: Loci): x is CameraAxesLoci {
return x.kind === 'data-loci' && x.tag === 'camera-axes';
}
function updateCamera(camera: Camera, viewport: Viewport, viewOffset: Camera.ViewOffset) {
const { near, far } = camera;
@@ -134,27 +174,52 @@ function updateCamera(camera: Camera, viewport: Viewport, viewOffset: Camera.Vie
function createAxesMesh(scale: number, mesh?: Mesh) {
const state = MeshBuilder.createState(512, 256, mesh);
const radius = 0.05 * scale;
const radius = 0.075 * scale;
const x = Vec3.scale(Vec3(), Vec3.unitX, scale);
const y = Vec3.scale(Vec3(), Vec3.unitY, scale);
const z = Vec3.scale(Vec3(), Vec3.unitZ, scale);
const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments: 32 };
state.currentGroup = 0;
state.currentGroup = CameraHelperAxis.None;
addSphere(state, Vec3.origin, radius, 2);
state.currentGroup = 1;
state.currentGroup = CameraHelperAxis.X;
addSphere(state, x, radius, 2);
addCylinder(state, Vec3.origin, x, 1, cylinderProps);
state.currentGroup = 2;
state.currentGroup = CameraHelperAxis.Y;
addSphere(state, y, radius, 2);
addCylinder(state, Vec3.origin, y, 1, cylinderProps);
state.currentGroup = 3;
state.currentGroup = CameraHelperAxis.Z;
addSphere(state, z, radius, 2);
addCylinder(state, Vec3.origin, z, 1, cylinderProps);
Vec3.scale(x, x, 0.5);
Vec3.scale(y, y, 0.5);
Vec3.scale(z, z, 0.5);
state.currentGroup = CameraHelperAxis.XY;
MeshBuilder.addTriangle(state, Vec3.origin, x, y);
MeshBuilder.addTriangle(state, Vec3.origin, y, x);
const xy = Vec3.add(Vec3(), x, y);
MeshBuilder.addTriangle(state, xy, x, y);
MeshBuilder.addTriangle(state, xy, y, x);
state.currentGroup = CameraHelperAxis.XZ;
MeshBuilder.addTriangle(state, Vec3.origin, x, z);
MeshBuilder.addTriangle(state, Vec3.origin, z, x);
const xz = Vec3.add(Vec3(), x, z);
MeshBuilder.addTriangle(state, xz, x, z);
MeshBuilder.addTriangle(state, xz, z, x);
state.currentGroup = CameraHelperAxis.YZ;
MeshBuilder.addTriangle(state, Vec3.origin, y, z);
MeshBuilder.addTriangle(state, Vec3.origin, z, y);
const yz = Vec3.add(Vec3(), y, z);
MeshBuilder.addTriangle(state, yz, y, z);
MeshBuilder.addTriangle(state, yz, z, y);
return MeshBuilder.getMesh(state);
}

View File

@@ -66,14 +66,20 @@ export class PickPass {
private renderVariant(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, variant: GraphicsRenderVariant) {
const depth = this.drawPass.depthTexturePrimitives;
renderer.clear(false);
renderer.update(camera);
renderer.renderPick(scene.primitives, camera, variant, null);
renderer.renderPick(scene.volumes, camera, variant, depth);
renderer.renderPick(helper.handle.scene, camera, variant, null);
if (helper.camera.isEnabled) {
helper.camera.update(camera);
renderer.update(helper.camera.camera);
renderer.renderPick(helper.camera.scene, helper.camera.camera, variant, null);
}
}
render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper) {
renderer.update(camera);
this.objectPickTarget.bind();
this.renderVariant(renderer, camera, scene, helper, 'pickObject');

View File

@@ -91,12 +91,21 @@ namespace Tokenizer {
return eatLine(state);
}
/** Advance the state by the given number of lines and return line as string. */
/** Advance the state and return line as string. */
export function readLine(state: Tokenizer): string {
markLine(state);
return getTokenString(state);
}
/** Advance the state and return trimmed line as string. */
export function readLineTrim(state: Tokenizer): string {
markLine(state);
const position = state.position;
trim(state, state.tokenStart, state.tokenEnd);
state.position = position;
return getTokenString(state);
}
function readLinesChunk(state: Tokenizer, count: number, tokens: Tokens) {
let read = 0;
for (let i = 0; i < count; i++) {

View File

@@ -32,7 +32,7 @@ function handleMolecule(tokenizer: Tokenizer): XyzFile['molecules'][number] {
const type_symbol = new Array<string>(count);
for (let i = 0; i < count; ++i) {
const line = Tokenizer.readLine(tokenizer);
const line = Tokenizer.readLineTrim(tokenizer);
const fields = line.split(/\s+/g);
type_symbol[i] = fields[0];
x[i] = +fields[1];

View File

@@ -111,33 +111,41 @@ namespace Spacegroup {
const _translationRef = Vec3();
const _translationRefSymop = Vec3();
const _translationRefOffset = Vec3();
const _translationSymop = Vec3();
export function setOperatorMatrixRef(spacegroup: Spacegroup, index: number, i: number, j: number, k: number, ref: Vec3, target: Mat4) {
Vec3.set(_ijkVec, i, j, k);
Vec3.floor(_translationRef, ref);
Mat4.copy(target, spacegroup.operators[index]);
Vec3.floor(_translationRefSymop, Vec3.transformMat4(_translationRefSymop, ref, target));
Mat4.getTranslation(_translationSymop, target);
Vec3.sub(_translationSymop, _translationSymop, _translationRefSymop);
Vec3.add(_translationSymop, _translationSymop, _translationRef);
Vec3.add(_translationSymop, _translationSymop, _ijkVec);
Mat4.setTranslation(target, _translationSymop);
Mat4.mul(target, spacegroup.cell.fromFractional, target);
Mat4.mul(target, target, spacegroup.cell.toFractional);
return target;
}
/**
* Get Symmetry operator for transformation around the given
* reference point `ref` in fractional coordinates
*/
export function getSymmetryOperatorRef(spacegroup: Spacegroup, spgrOp: number, i: number, j: number, k: number, ref: Vec3) {
const operator = setOperatorMatrixRef(spacegroup, spgrOp, i, j, k, ref, Mat4.zero());
return SymmetryOperator.create(`${spgrOp + 1}_${5 + i}${5 + j}${5 + k}`, operator, { hkl: Vec3.create(i, j, k), spgrOp });
const operator = Mat4.zero();
Vec3.set(_ijkVec, i, j, k);
Vec3.floor(_translationRef, ref);
Mat4.copy(operator, spacegroup.operators[spgrOp]);
Vec3.floor(_translationRefSymop, Vec3.transformMat4(_translationRefSymop, ref, operator));
Mat4.getTranslation(_translationSymop, operator);
Vec3.sub(_translationSymop, _translationSymop, _translationRefSymop);
Vec3.add(_translationSymop, _translationSymop, _translationRef);
Vec3.add(_translationSymop, _translationSymop, _ijkVec);
Mat4.setTranslation(operator, _translationSymop);
Mat4.mul(operator, spacegroup.cell.fromFractional, operator);
Mat4.mul(operator, operator, spacegroup.cell.toFractional);
Vec3.sub(_translationRefOffset, _translationRefSymop, _translationRef);
const _i = i - _translationRefOffset[0];
const _j = j - _translationRefOffset[1];
const _k = k - _translationRefOffset[2];
// const operator = setOperatorMatrixRef(spacegroup, spgrOp, i, j, k, ref, Mat4.zero());
return SymmetryOperator.create(`${spgrOp + 1}_${5 + _i}${5 + _j}${5 + _k}`, operator, { hkl: Vec3.create(_i, _j, _k), spgrOp });
}
function getOperatorMatrix(ids: number[]) {

View File

@@ -590,6 +590,9 @@ namespace Vec3 {
export const unitX: ReadonlyVec3 = create(1, 0, 0);
export const unitY: ReadonlyVec3 = create(0, 1, 0);
export const unitZ: ReadonlyVec3 = create(0, 0, 1);
export const negUnitX: ReadonlyVec3 = create(-1, 0, 0);
export const negUnitY: ReadonlyVec3 = create(0, -1, 0);
export const negUnitZ: ReadonlyVec3 = create(0, 0, -1);
}
export { Vec3 };

View File

@@ -37,9 +37,10 @@ export interface DataLoci<T = unknown, E = unknown> {
readonly kind: 'data-loci',
readonly tag: string
readonly data: T,
readonly elements: ReadonlyArray<E>
readonly elements: ReadonlyArray<E>,
getBoundingSphere(boundingSphere: Sphere3D): Sphere3D
/** if undefined, won't zoom */
getBoundingSphere?(boundingSphere: Sphere3D): Sphere3D
getLabel(): string
}
export function isDataLoci(x?: Loci): x is DataLoci {
@@ -159,7 +160,7 @@ namespace Loci {
} else if (loci.kind === 'group-loci') {
return ShapeGroup.getBoundingSphere(loci, boundingSphere);
} else if (loci.kind === 'data-loci') {
return loci.getBoundingSphere(boundingSphere);
return loci.getBoundingSphere?.(boundingSphere);
} else if (loci.kind === 'volume-loci') {
return Volume.getBoundingSphere(loci.volume, boundingSphere);
} else if (loci.kind === 'isosurface-loci') {

View File

@@ -23,6 +23,9 @@ import { StructureProperties } from '../properties';
import { BoundaryHelper } from '../../../../mol-math/geometry/boundary-helper';
import { Boundary } from '../../../../mol-math/geometry/boundary';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const osSize = OrderedSet.size;
/** Represents multiple structure element index locations */
export interface Loci {
readonly kind: 'element-loci',
@@ -71,7 +74,7 @@ export namespace Loci {
export function size(loci: Loci) {
let s = 0;
for (const u of loci.elements) s += OrderedSet.size(u.indices);
for (const u of loci.elements) s += osSize(u.indices);
return s;
}

View File

@@ -98,12 +98,12 @@ const residue = {
microheterogeneityCompIds: p(microheterogeneityCompIds),
secondary_structure_type: p(l => {
if (!Unit.isAtomic(l.unit)) notAtomic();
const secStruc = SecondaryStructureProvider.get(l.structure).value?.get(l.unit.id);
const secStruc = SecondaryStructureProvider.get(l.structure).value?.get(l.unit.invariantId);
return secStruc?.type[l.unit.residueIndex[l.element]] ?? SecondaryStructureType.Flag.NA;
}),
secondary_structure_key: p(l => {
if (!Unit.isAtomic(l.unit)) notAtomic();
const secStruc = SecondaryStructureProvider.get(l.structure).value?.get(l.unit.id);
const secStruc = SecondaryStructureProvider.get(l.structure).value?.get(l.unit.invariantId);
return secStruc?.key[l.unit.residueIndex[l.element]] ?? -1;
}),
chem_comp_type: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.chemicalComponentMap.get(compId(l))!.type),

View File

@@ -23,7 +23,7 @@ import { Carbohydrates } from './carbohydrates/data';
import { computeCarbohydrates } from './carbohydrates/compute';
import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
import { idFactory } from '../../../mol-util/id-factory';
import { Box3D, GridLookup3D } from '../../../mol-math/geometry';
import { GridLookup3D } from '../../../mol-math/geometry';
import { UUID } from '../../../mol-util';
import { CustomProperties } from '../../custom-property';
import { AtomicHierarchy } from '../model/properties/atomic';
@@ -50,7 +50,6 @@ class Structure {
interUnitBonds?: InterUnitBonds,
unitSymmetryGroups?: ReadonlyArray<Unit.SymmetryGroup>,
unitSymmetryGroupsIndexMap?: IntMap<number>,
unitsSortedByVolume?: ReadonlyArray<Unit>;
carbohydrates?: Carbohydrates,
models?: ReadonlyArray<Model>,
model?: Model,
@@ -69,6 +68,7 @@ class Structure {
uniqueElementCount: number,
atomicResidueCount: number,
polymerResidueCount: number,
polymerGapCount: number,
polymerUnitCount: number,
coordinateSystem: SymmetryOperator,
label: string,
@@ -82,6 +82,7 @@ class Structure {
uniqueElementCount: -1,
atomicResidueCount: -1,
polymerResidueCount: -1,
polymerGapCount: -1,
polymerUnitCount: -1,
coordinateSystem: SymmetryOperator.Default,
label: ''
@@ -136,6 +137,14 @@ class Structure {
return this._props.polymerResidueCount;
}
/** Count of all polymer gaps in the structure */
get polymerGapCount() {
if (this._props.polymerGapCount === -1) {
this._props.polymerGapCount = getPolymerGapCount(this);
}
return this._props.polymerGapCount;
}
get polymerUnitCount() {
if (this._props.polymerUnitCount === -1) {
this._props.polymerUnitCount = getPolymerUnitCount(this);
@@ -256,13 +265,6 @@ class Structure {
return this._props.unitSymmetryGroupsIndexMap;
}
/** Array of all units in the structure, sorted by their boundary volume */
get unitsSortedByVolume(): ReadonlyArray<Unit> {
if (this._props.unitsSortedByVolume) return this._props.unitsSortedByVolume;
this._props.unitsSortedByVolume = getUnitsSortedByVolume(this);
return this._props.unitsSortedByVolume;
}
get carbohydrates(): Carbohydrates {
if (this._props.carbohydrates) return this._props.carbohydrates;
this._props.carbohydrates = computeCarbohydrates(this);
@@ -429,24 +431,6 @@ function cmpUnits(units: ArrayLike<Unit>, i: number, j: number) {
return units[i].id - units[j].id;
}
function cmpUnitGroupVolume(units: ArrayLike<[index: number, volume: number]>, i: number, j: number) {
const d = units[i][1] - units[j][1];
if (d === 0) return units[i][0] - units[j][0];
return d;
}
function getUnitsSortedByVolume(structure: Structure) {
const { unitSymmetryGroups } = structure;
const groups = unitSymmetryGroups.map((g, i) => [i, Box3D.volume(g.units[0].lookup3d.boundary.box)] as [number, number]);
sort(groups, 0, groups.length, cmpUnitGroupVolume, arraySwap);
const ret: Unit[] = [];
for (const [i] of groups) {
for (const u of unitSymmetryGroups[i].units) {
ret.push(u);
}
}
return ret;
}
function getModels(s: Structure) {
const { units } = s;
@@ -568,6 +552,15 @@ function getPolymerResidueCount(structure: Structure): number {
return polymerResidueCount;
}
function getPolymerGapCount(structure: Structure): number {
const { units } = structure;
let polymerGapCount = 0;
for (let i = 0, _i = units.length; i < _i; i++) {
polymerGapCount += units[i].gapElements.length / 2;
}
return polymerGapCount;
}
function getPolymerUnitCount(structure: Structure): number {
const { units } = structure;
let polymerUnitCount = 0;

View File

@@ -23,6 +23,7 @@ import { OperatorNameColorThemeProvider } from '../../../mol-theme/color/operato
import { IndexPairBonds } from '../../../mol-model-formats/structure/property/bonds/index-pair';
import { StructConn } from '../../../mol-model-formats/structure/property/bonds/struct_conn';
import { StructureRepresentationRegistry } from '../../../mol-repr/structure/registry';
import { assertUnreachable } from '../../../mol-util/type-helpers';
export interface StructureRepresentationPresetProvider<P = any, S extends _Result = _Result> extends PresetProvider<PluginStateObject.Molecule.Structure, P, S> { }
export function StructureRepresentationPresetProvider<P, S extends _Result>(repr: StructureRepresentationPresetProvider<P, S>) { return repr; }
@@ -111,6 +112,8 @@ const auto = StructureRepresentationPresetProvider({
const thresholds = plugin.config.get(PluginConfig.Structure.SizeThresholds) || Structure.DefaultSizeThresholds;
const size = Structure.getSize(structure, thresholds);
const gapFraction = structure.polymerResidueCount / structure.polymerGapCount;
switch (size) {
case Structure.Size.Gigantic:
case Structure.Size.Huge:
@@ -118,10 +121,14 @@ const auto = StructureRepresentationPresetProvider({
case Structure.Size.Large:
return polymerCartoon.apply(ref, params, plugin);
case Structure.Size.Medium:
return polymerAndLigand.apply(ref, params, plugin);
if (gapFraction > 3) {
return polymerAndLigand.apply(ref, params, plugin);
} // else fall through
case Structure.Size.Small:
// `showCarbohydrateSymbol: true` is nice e.g. for PDB 1aga
// `showCarbohydrateSymbol: true` is nice, e.g., for PDB 1aga
return atomicDetail.apply(ref, { ...params, showCarbohydrateSymbol: true }, plugin);
default:
assertUnreachable(size);
}
}
});

View File

@@ -122,12 +122,11 @@ function applyMarkerAtomic(e: StructureElement.Loci.Element, action: MarkerActio
const { index: residueIndex } = model.atomicHierarchy.residueAtomSegments;
const { label_seq_id } = model.atomicHierarchy.residues;
let changed = false;
OrderedSet.forEachSegment(e.indices, i => residueIndex[elements[i]], rI => {
const seqId = label_seq_id.value(rI);
changed = applyMarkerActionAtPosition(markerArray, index(seqId), action) || changed;
applyMarkerActionAtPosition(markerArray, index(seqId), action);
});
return changed;
return true;
}
function applyMarkerCoarse(e: StructureElement.Loci.Element, action: MarkerAction, markerArray: Uint8Array, index: (seqId: number) => number) {
@@ -135,12 +134,11 @@ function applyMarkerCoarse(e: StructureElement.Loci.Element, action: MarkerActio
const begin = Unit.isSpheres(e.unit) ? model.coarseHierarchy.spheres.seq_id_begin : model.coarseHierarchy.gaussians.seq_id_begin;
const end = Unit.isSpheres(e.unit) ? model.coarseHierarchy.spheres.seq_id_end : model.coarseHierarchy.gaussians.seq_id_end;
let changed = false;
OrderedSet.forEach(e.indices, i => {
const eI = elements[i];
for (let s = index(begin.value(eI)), e = index(end.value(eI)); s <= e; s++) {
changed = applyMarkerActionAtPosition(markerArray, s, action) || changed;
applyMarkerActionAtPosition(markerArray, s, action);
}
});
return changed;
return true;
}

View File

@@ -7,8 +7,10 @@
import { StateTransformParameters } from '../mol-plugin-ui/state/common';
import { CreateVolumeStreamingBehavior } from '../mol-plugin/behavior/dynamic/volume-streaming/transformers';
import { DefaultPluginSpec, PluginSpec } from '../mol-plugin/spec';
import { StateAction, StateTransformer } from '../mol-state';
import { VolumeStreamingCustomControls } from './custom/volume';
export { PluginUISpec };
@@ -37,4 +39,7 @@ namespace PluginUISpec {
export const DefaultPluginUISpec = (): PluginUISpec => ({
...DefaultPluginSpec(),
customParamEditors: [
[CreateVolumeStreamingBehavior, VolumeStreamingCustomControls]
],
});

View File

@@ -11,6 +11,8 @@ import { PluginBehavior } from '../behavior';
import { ButtonsType, ModifiersKeys } from '../../../mol-util/input/input-observer';
import { Binding } from '../../../mol-util/binding';
import { PluginCommands } from '../../commands';
import { CameraHelperAxis, isCameraAxesLoci } from '../../../mol-canvas3d/helper/camera-helper';
import { Vec3 } from '../../../mol-math/linear-algebra';
const B = ButtonsType;
const M = ModifiersKeys;
@@ -62,4 +64,58 @@ export const FocusLoci = PluginBehavior.create<FocusLociProps>({
},
params: () => FocusLociParams,
display: { name: 'Camera Focus Loci on Canvas' }
});
export const CameraAxisHelper = PluginBehavior.create<{}>({
name: 'camera-axis-helper',
category: 'interaction',
ctor: class extends PluginBehavior.Handler<{}> {
register(): void {
let lastPlane = CameraHelperAxis.None;
let state = 0;
this.subscribeObservable(this.ctx.behaviors.interaction.click, ({ current }) => {
if (!this.ctx.canvas3d || !isCameraAxesLoci(current.loci)) return;
const axis = current.loci.elements[0].groupId;
if (axis === CameraHelperAxis.None) {
lastPlane = CameraHelperAxis.None;
state = 0;
return;
} else if (axis >= CameraHelperAxis.X && axis <= CameraHelperAxis.Z) {
lastPlane = CameraHelperAxis.None;
state = 0;
const up = Vec3();
up[axis - 1] = 1;
this.ctx.canvas3d.requestCameraReset({ snapshot: { up } });
} else {
if (lastPlane === axis) {
state = (state + 1) % 2;
} else {
lastPlane = axis;
state = 0;
}
let up: Vec3, dir: Vec3;
if (axis === CameraHelperAxis.XY) {
up = state ? Vec3.unitX : Vec3.unitY;
dir = Vec3.negUnitZ;
} else if (axis === CameraHelperAxis.XZ) {
up = state ? Vec3.unitX : Vec3.unitZ;
dir = Vec3.negUnitY;
} else {
up = state ? Vec3.unitY : Vec3.unitZ;
dir = Vec3.negUnitX;
}
this.ctx.canvas3d.requestCameraReset({
snapshot: (scene, camera) => camera.getInvariantFocus(scene.boundingSphereVisible.center, scene.boundingSphereVisible.radius, up, dir)
});
}
});
}
},
params: () => ({}),
display: { name: 'Camera Axis Helper' }
});

View File

@@ -111,6 +111,7 @@ export const DefaultPluginSpec = (): PluginSpec => ({
PluginSpec.Behavior(PluginBehaviors.Representation.DefaultLociLabelProvider),
PluginSpec.Behavior(PluginBehaviors.Representation.FocusLoci),
PluginSpec.Behavior(PluginBehaviors.Camera.FocusLoci),
PluginSpec.Behavior(PluginBehaviors.Camera.CameraAxisHelper),
PluginSpec.Behavior(StructureFocusRepresentation),
PluginSpec.Behavior(PluginBehaviors.CustomProps.StructureInfo),

View File

@@ -1,11 +1,12 @@
/**
* 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>
*/
import { OrderedSet, Interval } from '../mol-data/int';
import { BitFlags } from './bit-flags';
import { assertUnreachable } from './type-helpers';
export enum MarkerAction {
None = 0x0,
@@ -37,50 +38,85 @@ export namespace MarkerActions {
}
export function applyMarkerActionAtPosition(array: Uint8Array, i: number, action: MarkerAction) {
let v = array[i];
switch (action) {
case MarkerAction.Highlight:
if (v % 2 === 0) {
array[i] = v + 1;
return true;
}
return false;
case MarkerAction.RemoveHighlight:
if (v % 2 !== 0) {
array[i] = v - 1;
return true;
}
return false;
case MarkerAction.Select:
if (v < 2) {
array[i] = v + 2;
return true;
}
return false;
case MarkerAction.Deselect:
array[i] = v % 2;
return array[i] !== v;
case MarkerAction.Toggle:
if (v >= 2) array[i] = v - 2;
else array[i] = v + 2;
return true;
case MarkerAction.Clear:
array[i] = 0;
return v !== 0;
case MarkerAction.Highlight: array[i] |= 1; break;
case MarkerAction.RemoveHighlight: array[i] &= ~1; break;
case MarkerAction.Select: array[i] |= 2; break;
case MarkerAction.Deselect: array[i] &= ~2; break;
case MarkerAction.Toggle: array[i] ^= 2; break;
case MarkerAction.Clear: array[i] = 0; break;
}
return false;
}
export function applyMarkerAction(array: Uint8Array, set: OrderedSet, action: MarkerAction) {
let changed = false;
if (action === MarkerAction.None) return false;
if (Interval.is(set)) {
for (let i = Interval.start(set), _i = Interval.end(set); i < _i; i++) {
changed = applyMarkerActionAtPosition(array, i, action) || changed;
const start = Interval.start(set);
const end = Interval.end(set);
const view = new Uint32Array(array.buffer, 0, array.buffer.byteLength >> 2);
const viewStart = (start + 3) >> 2;
const viewEnd = viewStart + ((end - 4 * viewStart) >> 2);
const frontStart = start;
const frontEnd = Math.min(4 * viewStart, end);
const backStart = Math.max(start, 4 * viewEnd);
const backEnd = end;
switch (action) {
case MarkerAction.Highlight:
for (let i = viewStart; i < viewEnd; ++i) view[i] |= 0x01010101;
break;
case MarkerAction.RemoveHighlight:
for (let i = viewStart; i < viewEnd; ++i) view[i] &= ~0x01010101;
break;
case MarkerAction.Select:
for (let i = viewStart; i < viewEnd; ++i) view[i] |= 0x02020202;
break;
case MarkerAction.Deselect:
for (let i = viewStart; i < viewEnd; ++i) view[i] &= ~0x02020202;
break;
case MarkerAction.Toggle:
for (let i = viewStart; i < viewEnd; ++i) view[i] ^= 0x02020202;
break;
case MarkerAction.Clear:
for (let i = viewStart; i < viewEnd; ++i) view[i] = 0;
break;
default:
assertUnreachable(action);
}
for (let i = frontStart; i < frontEnd; ++i) {
applyMarkerActionAtPosition(array, i, action);
}
for (let i = backStart; i < backEnd; ++i) {
applyMarkerActionAtPosition(array, i, action);
}
} else {
for (let i = 0, _i = set.length; i < _i; i++) {
changed = applyMarkerActionAtPosition(array, set[i], action) || changed;
switch (action) {
case MarkerAction.Highlight:
for (let i = 0, il = set.length; i < il; ++i) array[set[i]] |= 1;
break;
case MarkerAction.RemoveHighlight:
for (let i = 0, il = set.length; i < il; ++i) array[set[i]] &= ~1;
break;
case MarkerAction.Select:
for (let i = 0, il = set.length; i < il; ++i) array[set[i]] |= 2;
break;
case MarkerAction.Deselect:
for (let i = 0, il = set.length; i < il; ++i) array[set[i]] &= ~2;
break;
case MarkerAction.Toggle:
for (let i = 0, il = set.length; i < il; ++i) array[set[i]] ^= 2;
break;
case MarkerAction.Clear:
for (let i = 0, il = set.length; i < il; ++i) array[set[i]] = 0;
break;
default:
assertUnreachable(action);
}
}
return changed;
return true;
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -25,4 +25,8 @@ export type NonNullableArray<T extends any[] | ReadonlyArray<any>> = T extends a
export function ObjectKeys<T extends object>(o: T) {
return Object.keys(o) as (keyof T)[];
}
export interface FiniteArray<T, L extends number = number> extends ReadonlyArray<T> { length: L };
export interface FiniteArray<T, L extends number = number> extends ReadonlyArray<T> { length: L };
export function assertUnreachable(x: never): never {
throw new Error('unreachable');
}

View File

@@ -53,6 +53,7 @@ export const indexTemplate = `<!DOCTYPE html>
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
syntaxHighlight: { activated: false, theme: 'agate' },
plugins: [
SwaggerUIBundle.plugins.DownloadUrl,
HidePlugin

View File

@@ -75,6 +75,7 @@ function remove(id: string) {
i++;
continue;
}
if (e.isSticky) return;
try {
for (let j = i + 1; j < index.length; j++) {
index[j - 1] = index[j];
@@ -89,15 +90,15 @@ function remove(id: string) {
}
}
function clear() {
let index = readIndex();
for (const e of index) {
try {
fs.unlinkSync(path.join(Config.working_folder, e.id + '.json'));
} catch { }
}
writeIndex([]);
}
// function clear() {
// let index = readIndex();
// for (const e of index) {
// try {
// fs.unlinkSync(path.join(Config.working_folder, e.id + '.json'));
// } catch { }
// }
// writeIndex([]);
// }
function mapPath(path: string) {
if (!Config.api_prefix) return path;
@@ -128,11 +129,11 @@ app.get(mapPath(`/get/:id`), (req, res) => {
});
});
app.get(mapPath(`/clear`), (req, res) => {
clear();
res.status(200);
res.end();
});
// app.get(mapPath(`/clear`), (req, res) => {
// clear();
// res.status(200);
// res.end();
// });
app.get(mapPath(`/remove/:id`), (req, res) => {
remove((req.params.id as string || '').toLowerCase());