mirror of
https://github.com/molstar/molstar.git
synced 2026-06-06 22:54:22 +08:00
Compare commits
1 Commits
batch-link
...
experiment
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14e619d6d2 |
@@ -24,7 +24,7 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
- `representation` node: support custom property `molstar_representation_params`
|
||||
- Add `backbone` and `line` representation types
|
||||
- `primitives` node: support custom property `molstar_mesh/label/line_params`
|
||||
- `canvas` node: support custom property `molstar_postprocessing` with the ability to customize outline, depth of field, bloom, shadow, occlusion (SSAO), fog, and background
|
||||
- `canvas` node: support custom property `molstar_postprocessing` with the ability to customize outline, depth of field, bloom, shadow, occlusion (SSAO), and fog
|
||||
- `clip` node support for structure and volume representations
|
||||
- `grid_slice` representation support for volumes
|
||||
- Support tethers and background for primitive labels
|
||||
@@ -44,7 +44,6 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
- Support loading trajectory coordinates from separate nodes
|
||||
- Trigger markdown commands from primitives using `molstar_markdown_commands` custom extensions
|
||||
- Support `molstar_on_load_markdown_commands` custom state on the `root` node
|
||||
- Print tree validation errors to plugin log
|
||||
- Added new color schemes, synchronized with D3.js ('inferno', 'magma', 'turbo', 'rainbow', 'sinebow', 'warm', 'cool', 'cubehelix-default', 'category-10', 'observable-10', 'tableau-10')
|
||||
- Snapshot Markdown improvements
|
||||
- Add `MarkdownExtensionManager` (`PluginContext.managers.markdownExtensions`)
|
||||
@@ -101,10 +100,6 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
- Add plugin config item ShowReset (shows/hides "Reset Zoom" button)
|
||||
- Fix transform params not being normalized when used together with param hash version
|
||||
- Replace `immer` with `mutative`
|
||||
- Fix renderer transparency check
|
||||
- VolumeServer & "VolumeCIF": default to P 1 spacegroup
|
||||
- Fix `ColorScale` for continuous case without offsets (broke in v4.13.0)
|
||||
- Experimental: support for custom color themes in Sequence Panel
|
||||
|
||||
## [v4.18.0] - 2025-06-08
|
||||
- MolViewSpec extension:
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "5.0.0-dev.13",
|
||||
"version": "5.0.0-dev.10",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "molstar",
|
||||
"version": "5.0.0-dev.13",
|
||||
"version": "5.0.0-dev.10",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/argparse": "^2.0.17",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "5.0.0-dev.13",
|
||||
"version": "5.0.0-dev.10",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -204,4 +204,4 @@
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,8 +131,8 @@ function getPaths(app) {
|
||||
|
||||
async function createBundle(app) {
|
||||
const { name, kind } = app;
|
||||
|
||||
const { prefix, entry, outfile } = getPaths(app);
|
||||
const NODE_ENV_PRD = isProduction || process.env.NODE_ENV === 'production';
|
||||
|
||||
const ctx = await esbuild.context({
|
||||
entryPoints: [entry],
|
||||
@@ -161,7 +161,6 @@ async function createBundle(app) {
|
||||
color: true,
|
||||
logLevel: 'info',
|
||||
define: {
|
||||
'process.env.NODE_ENV': JSON.stringify(NODE_ENV_PRD ? 'production' : 'development'),
|
||||
'process.env.DEBUG': JSON.stringify(process.env.DEBUG || false),
|
||||
__MOLSTAR_PLUGIN_VERSION__: JSON.stringify(VERSION),
|
||||
__MOLSTAR_BUILD_TIMESTAMP__: `${TIMESTAMP}`,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
*/
|
||||
|
||||
import { ArgumentParser } from 'argparse';
|
||||
import { treeSchemaToMarkdown, treeSchemaToString } from '../../extensions/mvs/tree/generic/tree-validation';
|
||||
import { treeSchemaToMarkdown, treeSchemaToString } from '../../extensions/mvs/tree/generic/tree-schema';
|
||||
import { MVSTreeSchema } from '../../extensions/mvs/tree/mvs/mvs-tree';
|
||||
|
||||
|
||||
|
||||
@@ -111,28 +111,12 @@ A story showcasing MolViewSpec animation capabilities.
|
||||
const builder = createMVSBuilder();
|
||||
|
||||
const _1cbs = structure(builder, '1cbs');
|
||||
const [poly, repr] = polymer(_1cbs, { color: Colors['1cbs'] });
|
||||
|
||||
repr.colorFromSource({
|
||||
ref: 'residue_colors',
|
||||
schema: 'residue',
|
||||
category_name: 'atom_site',
|
||||
field_name: 'label_comp_id',
|
||||
palette: {
|
||||
kind: 'categorical',
|
||||
missing_color: 'white',
|
||||
colors: {
|
||||
ALA: 'red',
|
||||
ILE: 'white',
|
||||
LYS: 'white',
|
||||
}
|
||||
}
|
||||
});
|
||||
const [poly,] = polymer(_1cbs, { color: Colors['1cbs'] });
|
||||
|
||||
const surface = poly.representation({
|
||||
type: 'surface',
|
||||
surface_type: 'gaussian',
|
||||
}).opacity({ opacity: 0.33 });
|
||||
});
|
||||
|
||||
_1cbs.component({ selector: 'ligand' })
|
||||
.transform({
|
||||
@@ -206,20 +190,6 @@ A story showcasing MolViewSpec animation capabilities.
|
||||
end: Colors['ligand-docked'],
|
||||
});
|
||||
|
||||
anim.interpolate({
|
||||
kind: 'color',
|
||||
target_ref: 'residue_colors',
|
||||
duration_ms: 2000,
|
||||
property: ['palette', 'colors'],
|
||||
start: {
|
||||
ALA: 'yellow',
|
||||
},
|
||||
end: {
|
||||
ILE: 'blue',
|
||||
LYS: 'purple',
|
||||
},
|
||||
});
|
||||
|
||||
return builder;
|
||||
},
|
||||
camera: {
|
||||
@@ -341,12 +311,10 @@ function structure(builder: Root, id: string): MVSStructure {
|
||||
.modelStructure();
|
||||
}
|
||||
|
||||
function polymer(structure: MVSStructure, options?: { color?: ColorT }) {
|
||||
function polymer(structure: MVSStructure, options: { color: ColorT }) {
|
||||
const component = structure.component({ selector: { label_asym_id: 'A' } });
|
||||
const reprensentation = component.representation({ type: 'cartoon' });
|
||||
if (options?.color) {
|
||||
reprensentation.color({ color: options.color });
|
||||
}
|
||||
reprensentation.color({ color: options.color });
|
||||
return [component, reprensentation] as const;
|
||||
}
|
||||
|
||||
@@ -364,21 +332,6 @@ export function buildStory(): MVSData_States {
|
||||
molstar_postprocessing: {
|
||||
enable_outline: true,
|
||||
enable_ssao: true,
|
||||
background: {
|
||||
name: 'horizontalGradient',
|
||||
params: {
|
||||
topColor: 0x777777,
|
||||
bottomColor: 0xffffff,
|
||||
}
|
||||
},
|
||||
// Example with background image:
|
||||
// background: {
|
||||
// name: 'image',
|
||||
// params: {
|
||||
// // URL can also be filename in MVSX archive
|
||||
// source: { name: 'url', params: 'URL' }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1 +1 @@
|
||||
Please refer to the standalone documentation [here](https://molstar.org/mol-view-spec-docs/).
|
||||
Find the MVS extension documentation [here](../../../docs/extensions/mvs/README.md).
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
import { Camera } from '../../mol-canvas3d/camera';
|
||||
import { CameraFogParams, Canvas3DParams, Canvas3DProps, DefaultCanvas3DParams } from '../../mol-canvas3d/canvas3d';
|
||||
import { TrackballControlsParams } from '../../mol-canvas3d/controls/trackball';
|
||||
import { BackgroundParams } from '../../mol-canvas3d/passes/background';
|
||||
import { BloomParams } from '../../mol-canvas3d/passes/bloom';
|
||||
import { DofParams } from '../../mol-canvas3d/passes/dof';
|
||||
import { OutlineParams } from '../../mol-canvas3d/passes/outline';
|
||||
@@ -22,7 +21,6 @@ import { PluginState } from '../../mol-plugin/state';
|
||||
import { StateObjectSelector } from '../../mol-state';
|
||||
import { fovAdjustedPosition } from '../../mol-util/camera';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { deepClone } from '../../mol-util/object';
|
||||
import { ParamDefinition } from '../../mol-util/param-definition';
|
||||
import { decodeColor } from './helpers/utils';
|
||||
import { MolstarLoadingContext } from './load';
|
||||
@@ -134,11 +132,6 @@ function optionalParams(enable: boolean | undefined, values: any, params: ParamD
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function normalizeBackground(variant: any, prev: any): any {
|
||||
if (!variant) return prev;
|
||||
return ParamDefinition.normalizeParams(BackgroundParams, { variant }, 'children');
|
||||
}
|
||||
|
||||
/** Create a deep copy of `oldCanvasProps` with values modified according to a canvas node params. */
|
||||
export function modifyCanvasProps(oldCanvasProps: Canvas3DProps, canvasNode: MolstarNode<'canvas'> | undefined, animationNode: MVSAnimationNode<'animation'> | undefined): Canvas3DProps {
|
||||
const params = canvasNode?.params;
|
||||
@@ -164,8 +157,6 @@ export function modifyCanvasProps(oldCanvasProps: Canvas3DProps, canvasNode: Mol
|
||||
const bloom = molstar_postprocessing?.enable_bloom;
|
||||
const bloomParams = molstar_postprocessing?.bloom_params;
|
||||
|
||||
const background = molstar_postprocessing?.background;
|
||||
|
||||
const trackballAnimation = animationNode?.custom?.molstar_trackball;
|
||||
const trackballAnimationName = trackballAnimation?.name;
|
||||
const trackballAnimationParams = trackballAnimation?.params ?? {};
|
||||
@@ -179,7 +170,6 @@ export function modifyCanvasProps(oldCanvasProps: Canvas3DProps, canvasNode: Mol
|
||||
occlusion: optionalParams(occlusion, occlusionParams, SsaoParams, oldCanvasProps.postprocessing.occlusion),
|
||||
dof: optionalParams(dof, dofParams, DofParams, oldCanvasProps.postprocessing.dof),
|
||||
bloom: optionalParams(bloom, bloomParams, BloomParams, oldCanvasProps.postprocessing.bloom),
|
||||
background: normalizeBackground(background, oldCanvasProps.postprocessing.background),
|
||||
},
|
||||
cameraFog: optionalParams(fog, fogParams, CameraFogParams, oldCanvasProps.cameraFog),
|
||||
renderer: {
|
||||
@@ -210,14 +200,13 @@ export function resetCanvasProps(plugin: PluginContext) {
|
||||
...old,
|
||||
postprocessing: {
|
||||
...old,
|
||||
outline: deepClone(DefaultCanvas3DParams.postprocessing.outline),
|
||||
shadow: deepClone(DefaultCanvas3DParams.postprocessing.shadow),
|
||||
occlusion: deepClone(DefaultCanvas3DParams.postprocessing.occlusion),
|
||||
dof: deepClone(DefaultCanvas3DParams.postprocessing.dof),
|
||||
bloom: deepClone(DefaultCanvas3DParams.postprocessing.bloom),
|
||||
background: deepClone(DefaultCanvas3DParams.postprocessing.background),
|
||||
outline: DefaultCanvas3DParams.postprocessing.outline,
|
||||
shadow: DefaultCanvas3DParams.postprocessing.shadow,
|
||||
occlusion: DefaultCanvas3DParams.postprocessing.occlusion,
|
||||
dof: DefaultCanvas3DParams.postprocessing.dof,
|
||||
bloom: DefaultCanvas3DParams.postprocessing.bloom,
|
||||
},
|
||||
cameraFog: deepClone(DefaultCanvas3DParams.cameraFog),
|
||||
cameraFog: DefaultCanvas3DParams.cameraFog,
|
||||
trackball: {
|
||||
...old?.trackball,
|
||||
animate: { name: 'off', params: {} },
|
||||
|
||||
@@ -12,7 +12,6 @@ import { EPSILON, Mat3, Mat4, Quat, Vec3 } from '../../../mol-math/linear-algebr
|
||||
import { RuntimeContext } from '../../../mol-task';
|
||||
import { deepEqual } from '../../../mol-util';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { decodeColor } from '../../../mol-util/color/utils';
|
||||
import { produce } from '../../../mol-util/produce';
|
||||
import { makeContinuousPaletteCheckpoints, MVSContinuousPaletteProps, MVSDiscretePaletteProps } from '../components/annotation-color-theme';
|
||||
import { palettePropsFromMVSPalette } from '../load-helpers';
|
||||
@@ -89,8 +88,6 @@ const EasingFnMap: Record<MVSAnimationEasing, (t: number) => number> = {
|
||||
|
||||
interface InterpolationCacheEntry {
|
||||
paletteFn?: (value: number) => Color,
|
||||
startColor?: Color | Record<number | string, Color>,
|
||||
endColor?: Color | Record<number | string, Color>,
|
||||
rotation?: { axis: Vec3, angle: number, start: Quat, end: Quat },
|
||||
}
|
||||
|
||||
@@ -206,15 +203,22 @@ function processScalarLike(transition: MVSAnimationNode<'interpolate'>, target:
|
||||
if (transition.params.kind === 'transform_matrix') return;
|
||||
if (previous && previous.params.kind === 'transform_matrix') return;
|
||||
|
||||
const startValue = transition.params.start ?? getPreviousScalarEnd(previous) ?? select(target, transition.params.property, offset);
|
||||
const startBase = transition.params.start ?? getPreviousScalarEnd(previous) ?? select(target, transition.params.property, offset);
|
||||
if (transition.params.kind === 'color' && !cacheEntry.paletteFn) {
|
||||
cacheEntry.paletteFn = makePaletteFunction(transition);
|
||||
cacheEntry.paletteFn = makePaletteFunction(transition, startBase, transition.params.end as ColorT | undefined);
|
||||
}
|
||||
|
||||
const endValue: any = transition.params.end;
|
||||
const paletteFn = cacheEntry.paletteFn!;
|
||||
|
||||
const startValue: any = transition.params.kind === 'color'
|
||||
? Color.toHexStyle(paletteFn(0))
|
||||
: startBase;
|
||||
const endValue: any = transition.params.kind === 'color'
|
||||
? Color.toHexStyle(paletteFn(1))
|
||||
: transition.params.end;
|
||||
|
||||
if (time <= 0) return startValue;
|
||||
else if (time >= 1 - EPSILON && !transition.params.alternate_direction && transition.params.kind !== 'color') return endValue;
|
||||
else if (time >= 1 - EPSILON && !transition.params.alternate_direction) return endValue;
|
||||
|
||||
let t = clamp(time, 0, 1);
|
||||
t = applyFrequency(t, transition.params.frequency ?? 1, !!transition.params.alternate_direction);
|
||||
@@ -229,13 +233,8 @@ function processScalarLike(transition: MVSAnimationNode<'interpolate'>, target:
|
||||
} else if (transition.params.kind === 'rotation_matrix') {
|
||||
return interpolateRotation(startValue, endValue, t, transition.params.noise_magnitude ?? 0, cacheEntry);
|
||||
} else if (transition.params.kind === 'color') {
|
||||
if (cacheEntry.paletteFn) {
|
||||
const color = cacheEntry.paletteFn(t);
|
||||
return Color.toHexStyle(color);
|
||||
}
|
||||
|
||||
const baseColors = typeof startValue === 'object' ? select(target, transition.params.property, offset) : undefined;
|
||||
return interpolateColors(startValue, endValue, t, cacheEntry, baseColors);
|
||||
const color = paletteFn(t);
|
||||
return Color.toHexStyle(color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,76 +441,6 @@ function interpolateRotation(start: Mat3, end: Mat3 | undefined, t: number, nois
|
||||
return Mat3.fromMat4(Mat3(), RotationState.temp);
|
||||
}
|
||||
|
||||
function decodeColors(color: ColorT | Record<number | string, ColorT> | undefined, baseColors: Record<number | string, ColorT> | undefined) {
|
||||
if (color === undefined || color === null) return undefined;
|
||||
|
||||
if (typeof color === 'object') {
|
||||
const ret: Record<number | string, Color> = {};
|
||||
if (baseColors) {
|
||||
for (const key of Object.keys(baseColors)) {
|
||||
const decoded = decodeColor(baseColors[key]);
|
||||
if (decoded !== undefined) {
|
||||
ret[key] = decoded;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const key of Object.keys(color)) {
|
||||
const decoded = decodeColor(color[key]);
|
||||
if (decoded !== undefined) {
|
||||
ret[key] = decoded;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
return decodeColor(color);
|
||||
}
|
||||
|
||||
function interpolateColors(start: ColorT | Record<number, ColorT>, end: ColorT | Record<number, ColorT> | undefined, time: number, cacheEntry: InterpolationCacheEntry, baseColors: Record<number, ColorT> | undefined) {
|
||||
const t = clamp(time, 0, 1);
|
||||
|
||||
if (cacheEntry.paletteFn) {
|
||||
const c = cacheEntry.paletteFn(t);
|
||||
return Color.toHexStyle(c);
|
||||
}
|
||||
|
||||
if (cacheEntry.startColor === undefined) {
|
||||
cacheEntry.startColor = decodeColors(start, baseColors);
|
||||
}
|
||||
if (cacheEntry.endColor === undefined) {
|
||||
cacheEntry.endColor = decodeColors(end, undefined);
|
||||
}
|
||||
|
||||
const { startColor, endColor } = cacheEntry;
|
||||
|
||||
if (typeof startColor === 'object') {
|
||||
if (typeof baseColors !== 'object') {
|
||||
throw new Error('Cannot interpolate from scalar color to color mapping');
|
||||
}
|
||||
|
||||
const ret = { ...baseColors as any, ...startColor as any };
|
||||
if (typeof endColor === 'object') {
|
||||
for (const key of Object.keys(endColor)) {
|
||||
ret[key] = Color.toHexStyle(Color.interpolate(startColor[key], endColor[key], t));
|
||||
}
|
||||
} else if (typeof endColor === 'number') {
|
||||
for (const key of Object.keys(startColor)) {
|
||||
ret[key] = Color.toHexStyle(Color.interpolate(startColor[key], endColor, t));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
if (typeof endColor === 'object') {
|
||||
throw new Error('Cannot interpolate from scalar color to color mapping');
|
||||
}
|
||||
|
||||
if (typeof endColor === 'number' && typeof startColor === 'number') {
|
||||
return Color.toHexStyle(Color.interpolate(startColor, endColor, t));
|
||||
}
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
function select(params: any, path: string | (string | number)[], offset: number) {
|
||||
if (typeof path === 'string') {
|
||||
return params?.[path];
|
||||
@@ -564,10 +493,12 @@ function makeNodeMap(tree: Tree, map: Map<string, (string | number)[]>, currentP
|
||||
return map;
|
||||
}
|
||||
|
||||
function makePaletteFunction(props: MVSAnimationNode<'interpolate'>): ((value: number) => Color) | undefined {
|
||||
if (props.params.kind !== 'color' || !props.params.palette) return undefined;
|
||||
function makePaletteFunction(props: MVSAnimationNode<'interpolate'>, start: ColorT | undefined | null, end: ColorT | undefined | null): ((value: number) => Color) | undefined {
|
||||
if (props.params.kind !== 'color') return undefined;
|
||||
|
||||
const params = palettePropsFromMVSPalette(props.params.palette);
|
||||
const params = props.params.palette
|
||||
? palettePropsFromMVSPalette(props.params.palette)
|
||||
: palettePropsFromMVSPalette({ kind: 'continuous', colors: [start ?? 'black', end ?? start ?? 'black'] });
|
||||
if (params.name === 'discrete') return makePaletteFunctionDiscrete(params.params);
|
||||
if (params.name === 'continuous') return makePaletteFunctionContinuous(params.params);
|
||||
throw new Error(`NotImplementedError: makePaletteFunction for ${(props as any).name}`);
|
||||
|
||||
@@ -33,8 +33,8 @@ import { NonCovalentInteractionsExtension } from './load-extensions/non-covalent
|
||||
import { LoadingActions, LoadingExtension, loadTreeVirtual, UpdateTarget } from './load-generic';
|
||||
import { AnnotationFromSourceKind, AnnotationFromUriKind, collectAnnotationReferences, collectAnnotationTooltips, collectInlineLabels, collectInlineTooltips, colorThemeForNode, componentFromXProps, componentPropsFromSelector, isPhantomComponent, labelFromXProps, makeNearestReprMap, prettyNameFromSelector, representationProps, structureProps, transformAndInstantiateStructure, transformAndInstantiateVolume, volumeColorThemeForNode, volumeRepresentationProps } from './load-helpers';
|
||||
import { MVSData, MVSData_States, Snapshot, SnapshotMetadata } from './mvs-data';
|
||||
import { MVSAnimationNode, MVSAnimationSchema } from './tree/animation/animation-tree';
|
||||
import { validateTree } from './tree/generic/tree-validation';
|
||||
import { MVSAnimationNode } from './tree/animation/animation-tree';
|
||||
import { validateTree } from './tree/generic/tree-schema';
|
||||
import { convertMvsToMolstar, mvsSanityCheck } from './tree/molstar/conversion';
|
||||
import { MolstarNode, MolstarNodeParams, MolstarSubtree, MolstarTree, MolstarTreeSchema } from './tree/molstar/molstar-tree';
|
||||
import { MVSTreeSchema } from './tree/mvs/mvs-tree';
|
||||
@@ -51,7 +51,6 @@ export interface MVSLoadOptions {
|
||||
sanityChecks?: boolean,
|
||||
/** Base for resolving relative URLs/URIs. May itself be a relative URL (relative to the window URL). */
|
||||
sourceUrl?: string,
|
||||
|
||||
doNotReportErrors?: boolean
|
||||
}
|
||||
|
||||
@@ -79,13 +78,10 @@ async function _loadMVS(ctx: RuntimeContext, plugin: PluginContext, data: MVSDat
|
||||
for (let i = 0; i < multiData.snapshots.length; i++) {
|
||||
const snapshot = multiData.snapshots[i];
|
||||
const previousSnapshot = i > 0 ? multiData.snapshots[i - 1] : multiData.snapshots[multiData.snapshots.length - 1];
|
||||
validateTree(MVSTreeSchema, snapshot.root, 'MVS', plugin);
|
||||
if (snapshot.animation) {
|
||||
validateTree(MVSAnimationSchema, snapshot.animation, 'Animation', plugin);
|
||||
}
|
||||
validateTree(MVSTreeSchema, snapshot.root, 'MVS');
|
||||
if (options.sanityChecks) mvsSanityCheck(snapshot.root);
|
||||
const molstarTree = convertMvsToMolstar(snapshot.root, options.sourceUrl);
|
||||
validateTree(MolstarTreeSchema, molstarTree, 'Converted Molstar', plugin);
|
||||
validateTree(MolstarTreeSchema, molstarTree, 'Converted Molstar');
|
||||
const entry = molstarTreeToEntry(
|
||||
plugin,
|
||||
molstarTree,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { treeValidationIssues } from './tree/generic/tree-validation';
|
||||
import { treeValidationIssues } from './tree/generic/tree-schema';
|
||||
import { treeToString } from './tree/generic/tree-utils';
|
||||
import { MVSAnimationSchema, MVSAnimationTree } from './tree/animation/animation-tree';
|
||||
import { Root, createMVSBuilder } from './tree/mvs/mvs-builder';
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { bool, float, int, list, OptionalField, RequiredField, str, union, nullable, literal, ValueFor, dict } from '../generic/field-schema';
|
||||
import { bool, float, int, list, OptionalField, RequiredField, str, union, nullable, literal, ValueFor } from '../generic/field-schema';
|
||||
import { SimpleParamsSchema, UnionParamsSchema } from '../generic/params-schema';
|
||||
import { NodeFor, ParamsOfKind, SubtreeOfKind, TreeFor, TreeSchema } from '../generic/tree-schema';
|
||||
import { ColorT, ContinuousPalette, DiscretePalette, Matrix, Vector3 } from '../mvs/param-types';
|
||||
@@ -75,8 +75,8 @@ const ColorInterpolation = {
|
||||
..._Common,
|
||||
..._Frequency,
|
||||
..._Easing,
|
||||
start: OptionalField(union(nullable(ColorT), dict(union(int, str), ColorT)), null, 'Start value. If unset, parent state value is used.'),
|
||||
end: OptionalField(union(nullable(ColorT), dict(union(int, str), ColorT)), null, 'End value.'),
|
||||
start: OptionalField(nullable(ColorT), null, 'Start value. If unset, parent state value is used.'),
|
||||
end: OptionalField(nullable(ColorT), null, 'End value.'),
|
||||
palette: OptionalField(nullable(union(DiscretePalette, ContinuousPalette)), null, 'Palette to sample colors from. Overrides start and end values.'),
|
||||
};
|
||||
|
||||
|
||||
@@ -4,8 +4,12 @@
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*/
|
||||
|
||||
import { mapObjectMap } from '../../../../mol-util/object';
|
||||
import { AllRequired, ParamsSchema, ValuesFor } from './params-schema';
|
||||
import { onelinerJsonString } from '../../../../mol-util/json';
|
||||
import { isPlainObject, mapObjectMap } from '../../../../mol-util/object';
|
||||
import { Field } from './field-schema';
|
||||
import { AllRequired, ParamsSchema, SimpleParamsSchema, ValuesFor, paramsValidationIssues } from './params-schema';
|
||||
import { treeToString } from './tree-utils';
|
||||
|
||||
|
||||
/** Type of "custom" of a tree node (key-value storage with arbitrary JSONable values) */
|
||||
export type CustomProps = Partial<Record<string, any>>
|
||||
@@ -110,3 +114,120 @@ export type NodeFor<TTreeSchema extends TreeSchema, TKind extends keyof ParamsSc
|
||||
|
||||
/** Type of tree which conforms to tree schema `TTreeSchema` */
|
||||
export type TreeFor<TTreeSchema extends TreeSchema> = Tree<NodeFor<TTreeSchema>, RootFor<TTreeSchema> & NodeFor<TTreeSchema>>
|
||||
|
||||
|
||||
/** Return `undefined` if a tree conforms to the given schema,
|
||||
* return validation issues (as a list of lines) if it does not conform.
|
||||
* If `options.requireAll`, all parameters (including optional) must have a value provided.
|
||||
* If `options.noExtra` is true, presence of any extra parameters is treated as an issue.
|
||||
* If `options.anyRoot` is true, the kind of the root node is not enforced.
|
||||
*/
|
||||
export function treeValidationIssues(schema: TreeSchema, tree: Tree, options: { requireAll?: boolean, noExtra?: boolean, anyRoot?: boolean, parent?: string } = {}): string[] | undefined {
|
||||
if (!isPlainObject(tree)) return [`Node must be an object, not ${tree}`];
|
||||
if (!options.anyRoot && tree.kind !== schema.rootKind) return [`Invalid root node kind "${tree.kind}", root must be of kind "${schema.rootKind}"`];
|
||||
const nodeSchema = schema.nodes[tree.kind];
|
||||
if (!nodeSchema) return [`Unknown node kind "${tree.kind}"`];
|
||||
if (nodeSchema.parent && (options.parent !== undefined) && !nodeSchema.parent.includes(options.parent)) {
|
||||
return [`Node of kind "${tree.kind}" cannot appear as a child of "${options.parent}". Allowed parents for "${tree.kind}" are: ${nodeSchema.parent.map(s => `"${s}"`).join(', ')}`];
|
||||
}
|
||||
const issues = paramsValidationIssues(nodeSchema.params, getParams(tree), options);
|
||||
if (issues) return [`Invalid parameters for node of kind "${tree.kind}":`, ...issues.map(s => ' ' + s)];
|
||||
if (tree.custom !== undefined && (typeof tree.custom !== 'object' || tree.custom === null)) {
|
||||
return [`Invalid "custom" for node of kind "${tree.kind}": must be an object, not ${tree.custom}.`];
|
||||
}
|
||||
for (const child of getChildren(tree)) {
|
||||
const issues = treeValidationIssues(schema, child, { ...options, anyRoot: true, parent: tree.kind });
|
||||
if (issues) return issues;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** Validate a tree against the given schema.
|
||||
* Do nothing if OK; print validation issues on console and throw an error is the tree does not conform.
|
||||
* Include `label` in the printed output. */
|
||||
export function validateTree(schema: TreeSchema, tree: Tree, label: string): void {
|
||||
const issues = treeValidationIssues(schema, tree, { noExtra: true });
|
||||
if (issues) {
|
||||
console.warn(`Invalid ${label} tree:\n${treeToString(tree)}`);
|
||||
console.error(`${label} tree validation issues:`);
|
||||
for (const line of issues) {
|
||||
console.error(' ', line);
|
||||
}
|
||||
throw new Error('FormatError');
|
||||
}
|
||||
}
|
||||
|
||||
/** Return documentation for a tree schema as plain text */
|
||||
export function treeSchemaToString<S extends TreeSchema>(schema: S): string {
|
||||
return treeSchemaToString_(schema, false);
|
||||
}
|
||||
/** Return documentation for a tree schema as markdown text */
|
||||
export function treeSchemaToMarkdown<S extends TreeSchema>(schema: S): string {
|
||||
return treeSchemaToString_(schema, true);
|
||||
}
|
||||
function treeSchemaToString_<S extends TreeSchema>(schema: S, markdown: boolean = false): string {
|
||||
const out: string[] = [];
|
||||
const bold = (str: string) => markdown ? `**${str}**` : str;
|
||||
const code = (str: string) => markdown ? `\`${str}\`` : str;
|
||||
const h1 = markdown ? '## ' : ' - ';
|
||||
const p1 = markdown ? '' : ' ';
|
||||
const h2 = markdown ? '- ' : ' - ';
|
||||
const p2 = markdown ? ' ' : ' ';
|
||||
const h3 = markdown ? ' - ' : ' - ';
|
||||
const p3 = markdown ? ' ' : ' ';
|
||||
const newline = markdown ? '\n\n' : '\n';
|
||||
out.push(`Tree schema:`);
|
||||
for (const kind in schema.nodes) {
|
||||
const { description, params, parent } = schema.nodes[kind];
|
||||
out.push(`${h1}${code(kind)}`);
|
||||
if (kind === schema.rootKind) {
|
||||
out.push(`${p1}[Root of the tree must be of this kind]`);
|
||||
}
|
||||
if (description) {
|
||||
out.push(`${p1}${description}`);
|
||||
}
|
||||
out.push(`${p1}Parent: ${!parent ? 'any' : parent.length === 0 ? 'none' : parent.map(code).join(' or ')}`);
|
||||
out.push(`${p1}Params:${Object.keys(params).length > 0 ? '' : ' none'}`);
|
||||
if (params.type === 'simple') {
|
||||
formatSimpleParams(out, params, { h: h2, p: p2, code, bold });
|
||||
} else {
|
||||
const key = params.discriminator;
|
||||
const casesStr = Object.keys(params.cases).join(' | ');
|
||||
out.push(`${h2}${bold(code(key + ': '))}${code(casesStr)}`);
|
||||
if (params.discriminatorDescription) {
|
||||
out.push(`${p2}${params.discriminatorDescription}`);
|
||||
}
|
||||
out.push(`${p2}[This parameter determines the rest of parameters]`);
|
||||
for (const case_ in params.cases) {
|
||||
const caseStr = `${params.discriminator}: "${case_}"`;
|
||||
out.push(`${p2}${bold(`Case ${code(caseStr)}:`)}`);
|
||||
formatSimpleParams(out, params.cases[case_], { h: h3, p: p3, code, bold });
|
||||
}
|
||||
}
|
||||
}
|
||||
return out.join(newline);
|
||||
}
|
||||
|
||||
function formatSimpleParams(out: string[], params: SimpleParamsSchema, formatting: { h: string, p: string, code: (str: string) => string, bold: (str: string) => string }): void {
|
||||
const { h, p, code, bold } = formatting;
|
||||
for (const key in params.fields) {
|
||||
const field = params.fields[key];
|
||||
out.push(`${h}${bold(code(key + (field.required ? ': ' : '?: ')))}${code(formatFieldType(field))}`);
|
||||
const defaultValue = field.required ? undefined : field.default;
|
||||
if (field.description) {
|
||||
out.push(`${p}${field.description}`);
|
||||
}
|
||||
if (defaultValue !== undefined) {
|
||||
out.push(`${p}Default: ${code(onelinerJsonString(defaultValue))}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function formatFieldType(field: Field): string {
|
||||
const typeString = field.type.name;
|
||||
if (typeString.startsWith('(') && typeString.endsWith(')')) {
|
||||
return typeString.slice(1, -1);
|
||||
} else {
|
||||
return typeString;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { onelinerJsonString } from '../../../../mol-util/json';
|
||||
import { isPlainObject } from '../../../../mol-util/object';
|
||||
import { Field } from './field-schema';
|
||||
import { SimpleParamsSchema, paramsValidationIssues } from './params-schema';
|
||||
import { getChildren, getParams, Tree, TreeSchema } from './tree-schema';
|
||||
import { treeToString } from './tree-utils';
|
||||
|
||||
/** Return `undefined` if a tree conforms to the given schema,
|
||||
* return validation issues (as a list of lines) if it does not conform.
|
||||
* If `options.requireAll`, all parameters (including optional) must have a value provided.
|
||||
* If `options.noExtra` is true, presence of any extra parameters is treated as an issue.
|
||||
* If `options.anyRoot` is true, the kind of the root node is not enforced.
|
||||
*/
|
||||
export function treeValidationIssues(schema: TreeSchema, tree: Tree, options: { requireAll?: boolean, noExtra?: boolean, anyRoot?: boolean, parent?: string } = {}): string[] | undefined {
|
||||
if (!isPlainObject(tree)) return [`Node must be an object, not ${tree}`];
|
||||
if (!options.anyRoot && tree.kind !== schema.rootKind) return [`Invalid root node kind "${tree.kind}", root must be of kind "${schema.rootKind}"`];
|
||||
const nodeSchema = schema.nodes[tree.kind];
|
||||
if (!nodeSchema) return [`Unknown node kind "${tree.kind}"`];
|
||||
if (nodeSchema.parent && (options.parent !== undefined) && !nodeSchema.parent.includes(options.parent)) {
|
||||
return [`Node of kind "${tree.kind}" cannot appear as a child of "${options.parent}". Allowed parents for "${tree.kind}" are: ${nodeSchema.parent.map(s => `"${s}"`).join(', ')}`];
|
||||
}
|
||||
const issues = paramsValidationIssues(nodeSchema.params, getParams(tree), options);
|
||||
if (issues) return [`Invalid parameters for node of kind "${tree.kind}":`, ...issues.map(s => ' ' + s)];
|
||||
if (tree.custom !== undefined && (typeof tree.custom !== 'object' || tree.custom === null)) {
|
||||
return [`Invalid "custom" for node of kind "${tree.kind}": must be an object, not ${tree.custom}.`];
|
||||
}
|
||||
for (const child of getChildren(tree)) {
|
||||
const issues = treeValidationIssues(schema, child, { ...options, anyRoot: true, parent: tree.kind });
|
||||
if (issues) return issues;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** Validate a tree against the given schema.
|
||||
* Do nothing if OK; print validation issues on console and throw an error is the tree does not conform.
|
||||
* Include `label` in the printed output. */
|
||||
export function validateTree(schema: TreeSchema, tree: Tree, label: string, plugin: PluginContext): void {
|
||||
const issues = treeValidationIssues(schema, tree, { noExtra: true });
|
||||
if (issues) {
|
||||
console.warn(`Invalid ${label} tree:\n${treeToString(tree)}`);
|
||||
console.error(`${label} tree validation issues:`);
|
||||
plugin.log.error(`${label} tree validation issues:`);
|
||||
for (const line of issues) {
|
||||
console.error(' ', line);
|
||||
plugin.log.error(line);
|
||||
}
|
||||
throw new Error('FormatError');
|
||||
}
|
||||
}
|
||||
|
||||
/** Return documentation for a tree schema as plain text */
|
||||
export function treeSchemaToString<S extends TreeSchema>(schema: S): string {
|
||||
return treeSchemaToString_(schema, false);
|
||||
}
|
||||
/** Return documentation for a tree schema as markdown text */
|
||||
export function treeSchemaToMarkdown<S extends TreeSchema>(schema: S): string {
|
||||
return treeSchemaToString_(schema, true);
|
||||
}
|
||||
function treeSchemaToString_<S extends TreeSchema>(schema: S, markdown: boolean = false): string {
|
||||
const out: string[] = [];
|
||||
const bold = (str: string) => markdown ? `**${str}**` : str;
|
||||
const code = (str: string) => markdown ? `\`${str}\`` : str;
|
||||
const h1 = markdown ? '## ' : ' - ';
|
||||
const p1 = markdown ? '' : ' ';
|
||||
const h2 = markdown ? '- ' : ' - ';
|
||||
const p2 = markdown ? ' ' : ' ';
|
||||
const h3 = markdown ? ' - ' : ' - ';
|
||||
const p3 = markdown ? ' ' : ' ';
|
||||
const newline = markdown ? '\n\n' : '\n';
|
||||
out.push(`Tree schema:`);
|
||||
for (const kind in schema.nodes) {
|
||||
const { description, params, parent } = schema.nodes[kind];
|
||||
out.push(`${h1}${code(kind)}`);
|
||||
if (kind === schema.rootKind) {
|
||||
out.push(`${p1}[Root of the tree must be of this kind]`);
|
||||
}
|
||||
if (description) {
|
||||
out.push(`${p1}${description}`);
|
||||
}
|
||||
out.push(`${p1}Parent: ${!parent ? 'any' : parent.length === 0 ? 'none' : parent.map(code).join(' or ')}`);
|
||||
out.push(`${p1}Params:${Object.keys(params).length > 0 ? '' : ' none'}`);
|
||||
if (params.type === 'simple') {
|
||||
formatSimpleParams(out, params, { h: h2, p: p2, code, bold });
|
||||
} else {
|
||||
const key = params.discriminator;
|
||||
const casesStr = Object.keys(params.cases).join(' | ');
|
||||
out.push(`${h2}${bold(code(key + ': '))}${code(casesStr)}`);
|
||||
if (params.discriminatorDescription) {
|
||||
out.push(`${p2}${params.discriminatorDescription}`);
|
||||
}
|
||||
out.push(`${p2}[This parameter determines the rest of parameters]`);
|
||||
for (const case_ in params.cases) {
|
||||
const caseStr = `${params.discriminator}: "${case_}"`;
|
||||
out.push(`${p2}${bold(`Case ${code(caseStr)}:`)}`);
|
||||
formatSimpleParams(out, params.cases[case_], { h: h3, p: p3, code, bold });
|
||||
}
|
||||
}
|
||||
}
|
||||
return out.join(newline);
|
||||
}
|
||||
|
||||
function formatSimpleParams(out: string[], params: SimpleParamsSchema, formatting: { h: string, p: string, code: (str: string) => string, bold: (str: string) => string }): void {
|
||||
const { h, p, code, bold } = formatting;
|
||||
for (const key in params.fields) {
|
||||
const field = params.fields[key];
|
||||
out.push(`${h}${bold(code(key + (field.required ? ': ' : '?: ')))}${code(formatFieldType(field))}`);
|
||||
const defaultValue = field.required ? undefined : field.default;
|
||||
if (field.description) {
|
||||
out.push(`${p}${field.description}`);
|
||||
}
|
||||
if (defaultValue !== undefined) {
|
||||
out.push(`${p}Default: ${code(onelinerJsonString(defaultValue))}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function formatFieldType(field: Field): string {
|
||||
const typeString = field.type.name;
|
||||
if (typeString.startsWith('(') && typeString.endsWith(')')) {
|
||||
return typeString.slice(1, -1);
|
||||
} else {
|
||||
return typeString;
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ const LinesParams = {
|
||||
vertices: RequiredField(FloatList, '3*n_vertices length array of floats with vertex position (x1, y1, z1, ...).'),
|
||||
/** 2*n_lines length array of indices into vertices that form lines (l1_1, l1_2, ...). */
|
||||
indices: RequiredField(IntList, '2*n_lines length array of indices into vertices that form lines (l1_1, l1_2, ...).'),
|
||||
/** Assign a number to each line to group them. If not specified, each line is considered a separate group (line i = group i). */
|
||||
/** Assign a number to each triangle to group them. If not specified, each line is considered a separate group (line i = group i). */
|
||||
line_groups: OptionalField(nullable(IntList), null, 'Assign a number to each triangle to group them. If not specified, each line is considered a separate group (line i = group i).'),
|
||||
/** Assign a color to each group. Where not assigned, uses `color`. */
|
||||
group_colors: OptionalField(dict(int, ColorT), {}, 'Assign a color to each group. Where not assigned, uses `color`.'),
|
||||
|
||||
@@ -445,9 +445,8 @@ function areImageTexturePropsEqual(sourceA: ImageProps['source'], sourceB: Image
|
||||
|
||||
function getImageTexture(ctx: WebGLContext, assetManager: AssetManager, source: ImageProps['source'], onload?: (errored?: boolean) => void): { texture: Texture, asset: Asset } {
|
||||
const asset = source.name === 'url'
|
||||
? assetManager.tryFindFilename(source.params) ?? Asset.getUrlAsset(assetManager, source.params)
|
||||
? Asset.getUrlAsset(assetManager, source.params)
|
||||
: source.params!;
|
||||
|
||||
if (typeof HTMLImageElement === 'undefined') {
|
||||
console.error(`Missing "HTMLImageElement" required for background image`);
|
||||
onload?.(true);
|
||||
|
||||
@@ -106,7 +106,6 @@ export class DrawPass {
|
||||
this.depthTextureOpaque.define(width, height);
|
||||
}
|
||||
|
||||
// TODO: link programs in parallel
|
||||
this.wboit = new WboitPass(webgl, width, height);
|
||||
this.dpoit = new DpoitPass(webgl, width, height);
|
||||
this.marking = new MarkingPass(webgl, width, height);
|
||||
|
||||
@@ -23,12 +23,6 @@ export class CommitQueue {
|
||||
return this.removeMap.size + this.addMap.size;
|
||||
}
|
||||
|
||||
forEachAdd(cb: (o: GraphicsRenderObject) => void) {
|
||||
for (let n = this.addList.first; n; n = n.next) {
|
||||
cb(n.value);
|
||||
}
|
||||
}
|
||||
|
||||
add(o: GraphicsRenderObject) {
|
||||
if (this.removeMap.has(o)) {
|
||||
const a = this.removeMap.get(o)!;
|
||||
|
||||
@@ -8,14 +8,14 @@ import { RenderableState, Renderable } from './renderable';
|
||||
import { idFactory } from '../mol-util/id-factory';
|
||||
import { WebGLContext } from './webgl/context';
|
||||
import { DirectVolumeValues, DirectVolumeRenderable } from './renderable/direct-volume';
|
||||
import { MeshValues, MeshRenderable, linkMeshRenderableShader } from './renderable/mesh';
|
||||
import { MeshValues, MeshRenderable } from './renderable/mesh';
|
||||
import { PointsValues, PointsRenderable } from './renderable/points';
|
||||
import { LinesValues, LinesRenderable } from './renderable/lines';
|
||||
import { SpheresValues, SpheresRenderable, linkSpheresRenderableShader } from './renderable/spheres';
|
||||
import { SpheresValues, SpheresRenderable } from './renderable/spheres';
|
||||
import { TextValues, TextRenderable } from './renderable/text';
|
||||
import { TextureMeshValues, TextureMeshRenderable } from './renderable/texture-mesh';
|
||||
import { ImageValues, ImageRenderable } from './renderable/image';
|
||||
import { CylindersRenderable, CylindersValues, linkCylindersRenderableShader } from './renderable/cylinders';
|
||||
import { CylindersRenderable, CylindersValues } from './renderable/cylinders';
|
||||
import { Transparency } from './webgl/render-item';
|
||||
|
||||
const getNextId = idFactory(0, 0x7FFFFFFF);
|
||||
@@ -63,13 +63,3 @@ export function createRenderable<T extends RenderObjectType>(ctx: WebGLContext,
|
||||
}
|
||||
throw new Error('unsupported type');
|
||||
}
|
||||
|
||||
export function linkRenderableShader<T extends RenderObjectType>(ctx: WebGLContext, o: GraphicsRenderObject<T>, transparency: Transparency) {
|
||||
switch (o.type) {
|
||||
case 'mesh': return linkMeshRenderableShader(ctx, o.id, o.values as MeshValues, o.state, o.materialId, transparency);
|
||||
case 'spheres': return linkSpheresRenderableShader(ctx, o.id, o.values as SpheresValues, o.state, o.materialId, transparency);
|
||||
case 'cylinders': return linkCylindersRenderableShader(ctx, o.id, o.values as CylindersValues, o.state, o.materialId, transparency);
|
||||
}
|
||||
console.log(o.type);
|
||||
// throw new Error('unsupported type');
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { Renderable, RenderableState, createRenderable } from '../renderable';
|
||||
import { WebGLContext } from '../webgl/context';
|
||||
import { createGraphicsRenderItem, GraphicsRenderVariants, linkRenderItemProgram, Transparency } from '../webgl/render-item';
|
||||
import { createGraphicsRenderItem, Transparency } from '../webgl/render-item';
|
||||
import { GlobalUniformSchema, BaseSchema, AttributeSpec, Values, InternalSchema, SizeSchema, InternalValues, ElementsSpec, ValueSpec, DefineSpec, GlobalTextureSchema, UniformSpec } from './schema';
|
||||
import { CylindersShaderCode } from '../shader-code';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
@@ -45,13 +45,4 @@ export function CylindersRenderable(ctx: WebGLContext, id: number, values: Cylin
|
||||
const shaderCode = CylindersShaderCode;
|
||||
const renderItem = createGraphicsRenderItem(ctx, 'triangles', shaderCode, schema, { ...values, ...internalValues }, materialId, transparency);
|
||||
return createRenderable(renderItem, values, state);
|
||||
}
|
||||
|
||||
export function linkCylindersRenderableShader(ctx: WebGLContext, id: number, values: CylindersValues, state: RenderableState, materialId: number, transparency: Transparency) {
|
||||
const schema = { ...GlobalUniformSchema, ...GlobalTextureSchema, ...InternalSchema, ...CylindersSchema };
|
||||
const internalValues: InternalValues = {
|
||||
uObjectId: ValueCell.create(id),
|
||||
};
|
||||
const shaderCode = CylindersShaderCode;
|
||||
linkRenderItemProgram(ctx, shaderCode, schema, { ...values, ...internalValues }, GraphicsRenderVariants, transparency);
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { Renderable, RenderableState, createRenderable } from '../renderable';
|
||||
import { WebGLContext } from '../webgl/context';
|
||||
import { createGraphicsRenderItem, GraphicsRenderVariants, linkRenderItemProgram, Transparency } from '../webgl/render-item';
|
||||
import { createGraphicsRenderItem, Transparency } from '../webgl/render-item';
|
||||
import { GlobalUniformSchema, BaseSchema, AttributeSpec, ElementsSpec, DefineSpec, Values, InternalSchema, InternalValues, GlobalTextureSchema, ValueSpec, UniformSpec } from './schema';
|
||||
import { MeshShaderCode } from '../shader-code';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
@@ -41,13 +41,4 @@ export function MeshRenderable(ctx: WebGLContext, id: number, values: MeshValues
|
||||
const renderItem = createGraphicsRenderItem(ctx, 'triangles', shaderCode, schema, { ...values, ...internalValues }, materialId, transparency);
|
||||
|
||||
return createRenderable(renderItem, values, state);
|
||||
}
|
||||
|
||||
export function linkMeshRenderableShader(ctx: WebGLContext, id: number, values: MeshValues, state: RenderableState, materialId: number, transparency: Transparency) {
|
||||
const schema = { ...GlobalUniformSchema, ...GlobalTextureSchema, ...InternalSchema, ...MeshSchema };
|
||||
const internalValues: InternalValues = {
|
||||
uObjectId: ValueCell.create(id),
|
||||
};
|
||||
const shaderCode = MeshShaderCode;
|
||||
linkRenderItemProgram(ctx, shaderCode, schema, { ...values, ...internalValues }, GraphicsRenderVariants, transparency);
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { Renderable, RenderableState, createRenderable } from '../renderable';
|
||||
import { WebGLContext } from '../webgl/context';
|
||||
import { createGraphicsRenderItem, GraphicsRenderVariants, linkRenderItemProgram, Transparency } from '../webgl/render-item';
|
||||
import { createGraphicsRenderItem, Transparency } from '../webgl/render-item';
|
||||
import { GlobalUniformSchema, BaseSchema, Values, InternalSchema, SizeSchema, InternalValues, ValueSpec, DefineSpec, GlobalTextureSchema, UniformSpec, TextureSpec } from './schema';
|
||||
import { SpheresShaderCode } from '../shader-code';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
@@ -46,13 +46,4 @@ export function SpheresRenderable(ctx: WebGLContext, id: number, values: Spheres
|
||||
const shaderCode = SpheresShaderCode;
|
||||
const renderItem = createGraphicsRenderItem(ctx, 'triangles', shaderCode, schema, { ...values, ...internalValues }, materialId, transparency);
|
||||
return createRenderable(renderItem, values, state);
|
||||
}
|
||||
|
||||
export function linkSpheresRenderableShader(ctx: WebGLContext, id: number, values: SpheresValues, state: RenderableState, materialId: number, transparency: Transparency) {
|
||||
const schema = { ...GlobalUniformSchema, ...GlobalTextureSchema, ...InternalSchema, ...SpheresSchema };
|
||||
const internalValues: InternalValues = {
|
||||
uObjectId: ValueCell.create(id),
|
||||
};
|
||||
const shaderCode = SpheresShaderCode;
|
||||
linkRenderItemProgram(ctx, shaderCode, schema, { ...values, ...internalValues }, GraphicsRenderVariants, transparency);
|
||||
}
|
||||
@@ -483,13 +483,13 @@ namespace Renderer {
|
||||
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
|
||||
const xrayShaded = r.values.dXrayShaded?.ref.value === 'on' || r.values.dXrayShaded?.ref.value === 'inverted';
|
||||
return (
|
||||
alpha !== 0 && (alpha < 1 ||
|
||||
(alpha < 1 && alpha !== 0) ||
|
||||
r.values.transparencyAverage.ref.value > 0 ||
|
||||
r.values.dGeometryType.ref.value === 'directVolume' ||
|
||||
r.values.dPointStyle?.ref.value === 'fuzzy' ||
|
||||
r.values.dGeometryType.ref.value === 'text' ||
|
||||
r.values.dGeometryType.ref.value === 'image' ||
|
||||
xrayShaded)
|
||||
xrayShaded
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import { WebGLContext } from './webgl/context';
|
||||
import { GraphicsRenderObject, createRenderable, linkRenderableShader as linkRenderableProgram } from './render-object';
|
||||
import { GraphicsRenderObject, createRenderable } from './render-object';
|
||||
import { Object3D } from './object3d';
|
||||
import { Sphere3D } from '../mol-math/geometry/primitives/sphere3d';
|
||||
import { CommitQueue } from './commit-queue';
|
||||
@@ -130,9 +130,7 @@ namespace Scene {
|
||||
|
||||
function add(o: GraphicsRenderObject) {
|
||||
if (!renderableMap.has(o)) {
|
||||
console.time(`create renderable ${o.type}`);
|
||||
const renderable = createRenderable(ctx, o, transparency);
|
||||
console.timeEnd(`create renderable ${o.type}`);
|
||||
renderables.push(renderable);
|
||||
if (o.type === 'direct-volume') {
|
||||
volumes.push(renderable);
|
||||
@@ -166,10 +164,6 @@ namespace Scene {
|
||||
|
||||
let i = 0;
|
||||
|
||||
// TODO: have to destroy programs that were not added (=removed before added)
|
||||
commitQueue.forEachAdd(o => linkRenderableProgram(ctx, o, transparency));
|
||||
// there could also be a separate queue just for program linking that calls program.finalize()
|
||||
|
||||
while (true) {
|
||||
const o = commitQueue.tryGetRemove();
|
||||
if (!o) break;
|
||||
|
||||
@@ -38,11 +38,11 @@ float getDepthOpaque(const in vec2 coords) {
|
||||
#endif
|
||||
}
|
||||
|
||||
vec2 getDepthTransparentWithAlpha(const in vec2 coords) {
|
||||
float getDepthTransparent(const in vec2 coords) {
|
||||
#ifdef dTransparentOutline
|
||||
return unpackRGBAToDepthWithAlpha(texture2D(tDepthTransparent, coords));
|
||||
return unpackRGBAToDepthWithAlpha(texture2D(tDepthTransparent, coords)).x;
|
||||
#else
|
||||
return vec2(1.0, 1.0);
|
||||
return 1.0;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ void main(void) {
|
||||
float selfViewZOpaque = isBackground(selfDepthOpaque) ? backgroundViewZ : getViewZ(selfDepthOpaque);
|
||||
float pixelSizeOpaque = getPixelSize(coords, selfDepthOpaque) * uOutlineThreshold;
|
||||
|
||||
float selfDepthTransparent = getDepthTransparentWithAlpha(coords).x;
|
||||
float selfDepthTransparent = getDepthTransparent(coords);
|
||||
float selfViewZTransparent = isBackground(selfDepthTransparent) ? backgroundViewZ : getViewZ(selfDepthTransparent);
|
||||
float pixelSizeTransparent = getPixelSize(coords, selfDepthTransparent) * uOutlineThreshold;
|
||||
|
||||
@@ -79,15 +79,12 @@ void main(void) {
|
||||
vec2 sampleCoords = coords + vec2(float(x), float(y)) * invTexSize;
|
||||
|
||||
float sampleDepthOpaque = getDepthOpaque(sampleCoords);
|
||||
vec2 sampleDepthTransparentWithAlpha = getDepthTransparentWithAlpha(sampleCoords);
|
||||
float sampleDepthTransparent = sampleDepthTransparentWithAlpha.x;
|
||||
float sampleAlphaTransparent = sampleDepthTransparentWithAlpha.y;
|
||||
float sampleDepthTransparent = getDepthTransparent(sampleCoords);
|
||||
|
||||
float sampleViewZOpaque = isBackground(sampleDepthOpaque) ? backgroundViewZ : getViewZ(sampleDepthOpaque);
|
||||
if (abs(selfViewZOpaque - sampleViewZOpaque) > pixelSizeOpaque && selfDepthOpaque > sampleDepthOpaque && sampleDepthOpaque <= bestDepth) {
|
||||
outline = 0.0;
|
||||
bestDepth = sampleDepthOpaque;
|
||||
transparentFlag = 0.0;
|
||||
}
|
||||
|
||||
if (sampleDepthTransparent < sampleDepthOpaque) {
|
||||
@@ -95,7 +92,7 @@ void main(void) {
|
||||
if (abs(selfViewZTransparent - sampleViewZTransparent) > pixelSizeTransparent && selfDepthTransparent > sampleDepthTransparent && sampleDepthTransparent <= bestDepth) {
|
||||
outline = 0.0;
|
||||
bestDepth = sampleDepthTransparent;
|
||||
transparentFlag = sampleAlphaTransparent;
|
||||
transparentFlag = 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,25 +168,15 @@ void main(void) {
|
||||
if (outline == 0.0) {
|
||||
float viewDist = abs(getViewZ(closestTexel));
|
||||
float fogFactor = smoothstep(uFogNear, uFogFar, viewDist);
|
||||
#ifdef dBlendTransparency
|
||||
if (isTransparentOutline > 0.0) {
|
||||
float outlineAlpha = clamp(isTransparentOutline * 2.0, 0.0, 1.0);
|
||||
transparentColor.a = transparentColor.a + outlineAlpha * (1.0 - fogFactor) * (1.0 - transparentColor.a);
|
||||
transparentColor.rgb = uOutlineColor;
|
||||
} else {
|
||||
if (!uTransparentBackground) {
|
||||
color.rgb = mix(uOutlineColor, uFogColor, fogFactor);
|
||||
} else {
|
||||
color.a = 1.0 - fogFactor;
|
||||
color.rgb = mix(uOutlineColor, vec3(0.0), fogFactor);
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (!uTransparentBackground) {
|
||||
if (!uTransparentBackground) {
|
||||
color.rgb = mix(uOutlineColor, uFogColor, fogFactor);
|
||||
} else {
|
||||
color.a = 1.0 - fogFactor;
|
||||
color.rgb = mix(uOutlineColor, vec3(0.0), fogFactor);
|
||||
} else {
|
||||
color.a = 1.0 - fogFactor;
|
||||
color.rgb = mix(uOutlineColor, vec3(0.0), fogFactor);
|
||||
}
|
||||
#ifdef dBlendTransparency
|
||||
if (isTransparentOutline == 1.0 || transparentDepth > closestTexel) {
|
||||
blendTransparency = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ export interface Program {
|
||||
readonly id: number
|
||||
|
||||
use: () => void
|
||||
finalize: () => void
|
||||
setUniforms: (uniformValues: UniformsList) => void
|
||||
uniform: (k: string, v: UniformType) => void
|
||||
bindAttributes: (attribueBuffers: AttributeBuffers) => void
|
||||
@@ -176,38 +175,29 @@ export function createProgram(gl: GLRenderingContext, state: WebGLState, extensi
|
||||
let locations: Locations;
|
||||
let uniformSetters: UniformSetters;
|
||||
|
||||
function link() {
|
||||
function init() {
|
||||
vertShader.attach(program);
|
||||
fragShader.attach(program);
|
||||
console.log('linking', programId)
|
||||
gl.linkProgram(program);
|
||||
if (isDebugMode) {
|
||||
checkProgram(gl, program);
|
||||
}
|
||||
|
||||
// if (isDebugMode) {
|
||||
// checkProgram(gl, program);
|
||||
// }
|
||||
locations = getLocations(gl, program, schema);
|
||||
uniformSetters = getUniformSetters(schema);
|
||||
|
||||
// locations = getLocations(gl, program, schema);
|
||||
// uniformSetters = getUniformSetters(schema);
|
||||
|
||||
// if (isDebugMode) {
|
||||
// checkActiveAttributes(gl, program, schema);
|
||||
// checkActiveUniforms(gl, program, schema);
|
||||
// }
|
||||
if (isDebugMode) {
|
||||
checkActiveAttributes(gl, program, schema);
|
||||
checkActiveUniforms(gl, program, schema);
|
||||
}
|
||||
}
|
||||
link();
|
||||
init();
|
||||
|
||||
let destroyed = false;
|
||||
|
||||
return {
|
||||
id: programId,
|
||||
|
||||
finalize: () => {
|
||||
if (locations) return;
|
||||
console.log('finalizing', programId)
|
||||
locations = getLocations(gl, program, schema);
|
||||
uniformSetters = getUniformSetters(schema);
|
||||
},
|
||||
|
||||
use: () => {
|
||||
// console.log('use', programId)
|
||||
state.currentProgramId = programId;
|
||||
@@ -255,7 +245,7 @@ export function createProgram(gl: GLRenderingContext, state: WebGLState, extensi
|
||||
|
||||
reset: () => {
|
||||
program = getProgram(gl);
|
||||
link();
|
||||
init();
|
||||
},
|
||||
destroy: () => {
|
||||
if (destroyed) return;
|
||||
|
||||
@@ -128,26 +128,6 @@ export function createComputeRenderItem(ctx: WebGLContext, drawMode: DrawMode, s
|
||||
return createRenderItem(ctx, drawMode, shaderCode, schema, values, materialId, ComputeRenderVariants, undefined);
|
||||
}
|
||||
|
||||
export function linkRenderItemProgram<T extends string>(ctx: WebGLContext, shaderCode: ShaderCode, schema: RenderableSchema, values: RenderableValues, renderVariants: T[], transparency: Transparency) {
|
||||
|
||||
// filter out unsupported variants
|
||||
renderVariants = renderVariants.filter(v => {
|
||||
if (v === 'tracing') return !!ctx.extensions.drawBuffers;
|
||||
return true;
|
||||
});
|
||||
|
||||
// TODO: optimize
|
||||
const { defineValues } = splitValues(schema, values);
|
||||
|
||||
for (const rv of renderVariants) {
|
||||
const defs = { ...defineValues, dRenderVariant: ValueCell.create(getRenderVariant(rv, transparency)) };
|
||||
if (schema.dRenderVariant === undefined) {
|
||||
Object.defineProperty(schema, 'dRenderVariant', { value: DefineSpec('string') });
|
||||
}
|
||||
ctx.resources.linkProgram(defs, shaderCode, schema);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a render item
|
||||
*
|
||||
|
||||
@@ -56,7 +56,6 @@ export interface WebGLResources {
|
||||
elements: (array: ElementsType, usageHint?: UsageHint) => ElementsBuffer
|
||||
pixelPack: (format: TextureFormat, type: TextureType) => PixelPackBuffer
|
||||
framebuffer: () => Framebuffer
|
||||
linkProgram: (defineValues: DefineValues, shaderCode: ShaderCode, schema: RenderableSchema) => Program
|
||||
program: (defineValues: DefineValues, shaderCode: ShaderCode, schema: RenderableSchema) => Program
|
||||
renderbuffer: (format: RenderbufferFormat, attachment: RenderbufferAttachment, width: number, height: number) => Renderbuffer
|
||||
shader: (type: ShaderType, source: string) => Shader
|
||||
@@ -138,14 +137,8 @@ export function createResources(gl: GLRenderingContext, state: WebGLState, stats
|
||||
framebuffer: () => {
|
||||
return wrap('framebuffer', createFramebuffer(gl));
|
||||
},
|
||||
linkProgram: (defineValues: DefineValues, shaderCode: ShaderCode, schema: RenderableSchema) => {
|
||||
return wrapCached(programCache.get({ defineValues, shaderCode, schema }));
|
||||
},
|
||||
program: (defineValues: DefineValues, shaderCode: ShaderCode, schema: RenderableSchema) => {
|
||||
// TODO: create function
|
||||
const linked = wrapCached(programCache.get({ defineValues, shaderCode, schema }));
|
||||
linked.finalize();
|
||||
return linked;
|
||||
return wrapCached(programCache.get({ defineValues, shaderCode, schema }));
|
||||
},
|
||||
renderbuffer: (format: RenderbufferFormat, attachment: RenderbufferAttachment, width: number, height: number) => {
|
||||
return wrap('renderbuffer', createRenderbuffer(gl, format, attachment, width, height));
|
||||
|
||||
@@ -15,8 +15,7 @@ export { MinimizeRmsd };
|
||||
namespace MinimizeRmsd {
|
||||
export interface Result {
|
||||
bTransform: Mat4,
|
||||
rmsd: number,
|
||||
nAlignedElements: number,
|
||||
rmsd: number
|
||||
}
|
||||
|
||||
export interface Positions { x: ArrayLike<number>, y: ArrayLike<number>, z: ArrayLike<number> }
|
||||
@@ -34,7 +33,7 @@ namespace MinimizeRmsd {
|
||||
}
|
||||
|
||||
export function compute(data: Input, result?: MinimizeRmsd.Result) {
|
||||
result ??= { bTransform: Mat4.zero(), rmsd: 0.0, nAlignedElements: 0 };
|
||||
if (typeof result === 'undefined') result = { bTransform: Mat4.zero(), rmsd: 0.0 };
|
||||
findMinimalRmsdTransformImpl(new RmsdTransformState(data, result));
|
||||
return result;
|
||||
}
|
||||
@@ -171,5 +170,4 @@ function findMinimalRmsdTransformImpl(state: RmsdTransformState): void {
|
||||
rmsd = rmsd < 0.0 ? 0.0 : Math.sqrt(rmsd / state.a.x.length);
|
||||
makeTransformMatrix(state);
|
||||
state.result.rmsd = rmsd;
|
||||
state.result.nAlignedElements = state.a.x.length;
|
||||
}
|
||||
@@ -16,7 +16,7 @@ export function volumeFromDensityServerData(source: DensityServer_Data_Database,
|
||||
return Task.create<Volume>('Create Volume', async ctx => {
|
||||
const { volume_data_3d_info: info, volume_data_3d: values } = source;
|
||||
const cell = SpacegroupCell.create(
|
||||
info.spacegroup_number.value(0) || 'P 1',
|
||||
info.spacegroup_number.value(0),
|
||||
Vec3.ofArray(info.spacegroup_cell_size.value(0)),
|
||||
Vec3.scale(Vec3.zero(), Vec3.ofArray(info.spacegroup_cell_angles.value(0)), Math.PI / 180)
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@ export function volumeFromSegmentationData(source: Segmentation_Data_Database, p
|
||||
return Task.create<Volume>('Create Segmentation Volume', async ctx => {
|
||||
const { volume_data_3d_info: info, segmentation_data_3d: values } = source;
|
||||
const cell = SpacegroupCell.create(
|
||||
info.spacegroup_number.value(0) || 'P 1',
|
||||
info.spacegroup_number.value(0),
|
||||
Vec3.ofArray(info.spacegroup_cell_size.value(0)),
|
||||
Vec3.scale(Vec3(), Vec3.ofArray(info.spacegroup_cell_angles.value(0)), Math.PI / 180)
|
||||
);
|
||||
|
||||
@@ -13,11 +13,9 @@ async function setPartialSnapshot(plugin: PluginContext, entry: Partial<PluginSt
|
||||
if (entry.data) {
|
||||
await plugin.runTask(plugin.state.data.setSnapshot(entry.data));
|
||||
// update the canvas3d trackball with the snapshot
|
||||
if (entry.canvas3d?.props?.trackball) {
|
||||
plugin.canvas3d?.setProps({
|
||||
trackball: entry.canvas3d?.props?.trackball
|
||||
});
|
||||
}
|
||||
plugin.canvas3d?.setProps({
|
||||
trackball: entry.canvas3d?.props?.trackball
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ import { elementLabel } from '../mol-theme/label';
|
||||
import { Icon, HelpOutlineSvg } from './controls/icons';
|
||||
import { StructureSelectionManager } from '../mol-plugin-state/manager/structure/selection';
|
||||
import { arrayEqual } from '../mol-util/array';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ThemeProvider } from '../mol-theme/theme';
|
||||
|
||||
const MaxDisplaySequenceLength = 5000;
|
||||
// TODO: add virtualized Select controls (at best with a search box)?
|
||||
@@ -244,6 +246,13 @@ export class SequenceView extends PluginUIComponent<{ defaultMode?: SequenceView
|
||||
}
|
||||
});
|
||||
|
||||
const experimentalSequenceTheme = this.plugin.customUIState.experimentalSequenceTheme as Observable<ThemeProvider<any, any> | undefined> | undefined;
|
||||
if (experimentalSequenceTheme) {
|
||||
this.subscribe(experimentalSequenceTheme as Observable<ThemeProvider<any, any>>, theme => {
|
||||
// do stuff
|
||||
});
|
||||
}
|
||||
|
||||
const modeOptions = this.plugin.spec.components?.sequenceViewer?.modeOptions;
|
||||
if (modeOptions) {
|
||||
const modeSet = new Set(modeOptions);
|
||||
|
||||
@@ -47,7 +47,7 @@ export class ChainSequenceWrapper extends SequenceWrapper<StructureUnit> {
|
||||
return Interval.Empty;
|
||||
}
|
||||
|
||||
override getLoci(seqIdx: number) {
|
||||
getLoci(seqIdx: number) {
|
||||
return this.loci;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export class ElementSequenceWrapper extends SequenceWrapper<StructureUnit> {
|
||||
return Interval.Empty;
|
||||
}
|
||||
|
||||
override getLoci(seqIdx: number) {
|
||||
getLoci(seqIdx: number) {
|
||||
const { units } = this.data;
|
||||
const lociElements: StructureElement.Loci['elements'][0][] = [];
|
||||
let offset = 0;
|
||||
|
||||
@@ -53,7 +53,7 @@ export class HeteroSequenceWrapper extends SequenceWrapper<StructureUnit> {
|
||||
return Interval.Empty;
|
||||
}
|
||||
|
||||
override getLoci(seqIdx: number) {
|
||||
getLoci(seqIdx: number) {
|
||||
const elements: StructureElement.Loci['elements'][0][] = [];
|
||||
const rI = this.residueIndices.get(seqIdx);
|
||||
if (rI !== undefined) {
|
||||
|
||||
@@ -67,7 +67,7 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> {
|
||||
return Interval.Empty;
|
||||
}
|
||||
|
||||
override getLoci(seqIdx: number) {
|
||||
getLoci(seqIdx: number) {
|
||||
const query = createResidueQuery(this.data.units[0].chainGroupId, this.data.units[0].conformation.operator.name, this.seqId(seqIdx));
|
||||
return StructureSelection.toLociWithSourceUnits(StructureQuery.run(query, this.data.structure));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2024 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>
|
||||
@@ -7,21 +7,16 @@
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { BehaviorSubject, Subject } from 'rxjs';
|
||||
import { Subject } from 'rxjs';
|
||||
import { throttleTime } from 'rxjs/operators';
|
||||
import { OrderedSet } from '../../mol-data/int';
|
||||
import { ColorTypeLocation } from '../../mol-geo/geometry/color-data';
|
||||
import { EveryLoci } from '../../mol-model/loci';
|
||||
import { StructureElement, StructureProperties, Unit } from '../../mol-model/structure';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { Representation } from '../../mol-repr/representation';
|
||||
import { Task } from '../../mol-task';
|
||||
import { ColorTheme, LocationColor } from '../../mol-theme/color';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { ButtonsType, getButton, getButtons, getModifiers, ModifiersKeys } from '../../mol-util/input/input-observer';
|
||||
import { MarkerAction } from '../../mol-util/marker-action';
|
||||
import { memoizeLatest } from '../../mol-util/memoize';
|
||||
import { PluginUIComponent } from '../base';
|
||||
import { SequenceWrapper } from './wrapper';
|
||||
|
||||
@@ -41,19 +36,12 @@ const DefaultMarkerColors = {
|
||||
focused: '',
|
||||
};
|
||||
|
||||
type ColorThemeProvider = ColorTheme.Provider<any, string, ColorTypeLocation> | undefined
|
||||
|
||||
|
||||
// TODO: this is somewhat inefficient and should be done using a canvas.
|
||||
export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
|
||||
protected parentDiv = React.createRef<HTMLDivElement>();
|
||||
protected lastMouseOverSeqIdx = -1;
|
||||
protected highlightQueue = new Subject<{ seqIdx: number, buttons: number, button: number, modifiers: ModifiersKeys }>();
|
||||
protected markerColors = { ...DefaultMarkerColors };
|
||||
/** @experimental */
|
||||
private customColorThemeWrapper: ColorThemeWrapper | undefined = undefined;
|
||||
/** @experimental Custom function that assigns color to residues in the Sequence component (unless highlighted or selected) */
|
||||
private customColorFunction: ((idx: number) => string) | undefined = undefined;
|
||||
|
||||
protected lociHighlightProvider = (loci: Representation.Loci, action: MarkerAction) => {
|
||||
const changed = this.props.sequenceWrapper.markResidue(loci.loci, action);
|
||||
@@ -93,14 +81,6 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
|
||||
this.updateColors();
|
||||
this.updateMarker();
|
||||
});
|
||||
const experimentalSequenceColorTheme: BehaviorSubject<ColorThemeProvider> | undefined = this.plugin.customUIState.experimentalSequenceColorTheme;
|
||||
if (experimentalSequenceColorTheme) {
|
||||
this.subscribe(experimentalSequenceColorTheme, theme => {
|
||||
if (!theme && !this.customColorThemeWrapper) return;
|
||||
this.customColorThemeWrapper = ColorThemeWrapper(this.plugin, theme, () => this.forceUpdate());
|
||||
this.forceUpdate();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateColors() {
|
||||
@@ -219,12 +199,9 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
|
||||
|
||||
protected getBackgroundColor(seqIdx: number) {
|
||||
const seqWrapper = this.props.sequenceWrapper;
|
||||
if (seqWrapper.isHighlighted(seqIdx) && this.markerColors.highlighted) return this.markerColors.highlighted;
|
||||
if (seqWrapper.isSelected(seqIdx) && this.markerColors.selected) return this.markerColors.selected;
|
||||
if (seqWrapper.isFocused(seqIdx) && this.markerColors.focused) return this.markerColors.focused;
|
||||
if (this.customColorFunction) {
|
||||
return this.customColorFunction(seqIdx);
|
||||
}
|
||||
if (seqWrapper.isHighlighted(seqIdx)) return this.markerColors.highlighted;
|
||||
if (seqWrapper.isSelected(seqIdx)) return this.markerColors.selected;
|
||||
if (seqWrapper.isFocused(seqIdx)) return this.markerColors.focused;
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -345,11 +322,8 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
|
||||
|
||||
render() {
|
||||
const sw = this.props.sequenceWrapper;
|
||||
const elems: JSX.Element[] = [];
|
||||
|
||||
if (this.customColorThemeWrapper) {
|
||||
this.customColorFunction = this.customColorThemeWrapper.getColorFunction(sw);
|
||||
}
|
||||
const elems: JSX.Element[] = [];
|
||||
|
||||
const hasNumbers = !this.props.hideSequenceNumbers, period = this.sequenceNumberPeriod;
|
||||
for (let i = 0, il = sw.length; i < il; ++i) {
|
||||
@@ -381,57 +355,3 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type ColorThemeWrapper = ReturnType<typeof ColorThemeWrapper>
|
||||
|
||||
function ColorThemeWrapper(plugin: PluginContext, theme: ColorThemeProvider, forceUpdate: () => void) {
|
||||
const tmpLocation = StructureElement.Location.create();
|
||||
|
||||
function computeColor(sequenceWrapper: SequenceWrapper.Any, idx: number, locationColor: LocationColor) {
|
||||
const loci = sequenceWrapper.getLoci(idx);
|
||||
if (!loci || StructureElement.Loci.isEmpty(loci)) return '';
|
||||
StructureElement.Loci.getFirstLocation(loci, tmpLocation);
|
||||
const color = locationColor(tmpLocation, false);
|
||||
if (color < 0) return ''; // Color(-1) is used as special value NoColor
|
||||
return Color.toHexStyle(color);
|
||||
}
|
||||
|
||||
const getColorFunction = memoizeLatest((sequenceWrapper: SequenceWrapper.Any) => {
|
||||
if (!theme) return undefined;
|
||||
const structure = sequenceWrapper.getLoci(0)?.structure;
|
||||
if (!structure) return undefined;
|
||||
|
||||
let themeColor: LocationColor | undefined = undefined;
|
||||
if (theme.ensureCustomProperties) {
|
||||
// The following task runs asynchronously
|
||||
plugin.runTask(Task.create('Attach custom properties for coloring theme', async runtime => {
|
||||
try {
|
||||
await theme.ensureCustomProperties?.attach({ assetManager: plugin.managers.asset, runtime }, { structure });
|
||||
} catch (err) {
|
||||
console.warn(`Failed to attach custom properties needed for coloring theme ${theme.name}:`, err);
|
||||
} finally {
|
||||
themeColor = theme.factory({ structure }, theme.defaultValues).color;
|
||||
forceUpdate();
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
themeColor = theme.factory({ structure }, theme.defaultValues).color;
|
||||
}
|
||||
|
||||
const cache: { [idx: number]: string } = {};
|
||||
|
||||
return (idx: number) => {
|
||||
if (themeColor) { // custom properties ready
|
||||
return cache[idx] ??= computeColor(sequenceWrapper, idx, themeColor);
|
||||
} else { // custom properties not ready
|
||||
return '';
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
themeName: theme?.name,
|
||||
getColorFunction,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -83,15 +83,6 @@ class AssetManager {
|
||||
}
|
||||
}
|
||||
|
||||
tryFindFilename(name: string): Asset | undefined {
|
||||
const it = this._assets.values();
|
||||
while (true) {
|
||||
const { done, value } = it.next();
|
||||
if (done) break;
|
||||
if (value.file.name === name) return value.asset;
|
||||
}
|
||||
}
|
||||
|
||||
set(asset: Asset, file: File, options?: { isStatic?: boolean, tag?: string }) {
|
||||
this._assets.set(asset.id, { asset, file, refCount: 0, tag: options?.tag, isStatic: options?.isStatic });
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ export namespace ColorScale {
|
||||
}
|
||||
} else {
|
||||
switch (type) {
|
||||
case 'continuous': color = (value: number) => valueToColor(value, colors, min, diff); break;
|
||||
case 'continuous': color = (value: number) => valueToColor(value, colors, min, max, diff); break;
|
||||
case 'discrete': color = (value: number) => valueToDiscreteColor(value, colors, min, max, diff); break;
|
||||
}
|
||||
}
|
||||
@@ -113,8 +113,8 @@ export namespace ColorScale {
|
||||
return Color.interpolate(src[i - 1], src[i], t1);
|
||||
}
|
||||
|
||||
function valueToColor(value: number, colors: ColorListEntry[], min: number, diff: number) {
|
||||
const t = Math.min(colors.length - 1, Math.max(0, ((value - min) / diff) * (colors.length - 1)));
|
||||
function valueToColor(value: number, colors: ColorListEntry[], min: number, max: number, diff: number) {
|
||||
const t = Math.min(colors.length - 1, Math.max(0, ((value - min) / diff) * colors.length - 1));
|
||||
const tf = Math.floor(t);
|
||||
const c1 = colors[tf] as Color;
|
||||
const c2 = colors[Math.ceil(t)] as Color;
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
*/
|
||||
|
||||
export class ErrorContext {
|
||||
private errors: { [tag: string]: string[] } = Object.create(null);
|
||||
private errors: { [tag: string]: any[] } = Object.create(null);
|
||||
|
||||
get(tag: string): ReadonlyArray<string> {
|
||||
get(tag: string): ReadonlyArray<any> {
|
||||
return this.errors[tag] ?? [];
|
||||
}
|
||||
|
||||
add(tag: string, error: string) {
|
||||
add(tag: string, error: any) {
|
||||
if (tag in this.errors && Array.isArray(this.errors[tag])) {
|
||||
this.errors[tag].push(error);
|
||||
} else {
|
||||
|
||||
@@ -4,12 +4,20 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { create } from 'mutative/dist/index.js';
|
||||
import { create, rawReturn } from 'mutative';
|
||||
|
||||
let currentRecipe: any = undefined;
|
||||
function recipeWrapper(draft: any) {
|
||||
const r = currentRecipe(draft);
|
||||
if (r !== undefined && r !== draft) return rawReturn(r);
|
||||
return r;
|
||||
}
|
||||
|
||||
/** Apply changes to an immutable-like object */
|
||||
export function produce<T>(base: T, recipe: (draft: T) => T | void): T {
|
||||
currentRecipe = recipe;
|
||||
if (typeof base === 'object' && !('prototype' in (base as any))) {
|
||||
return create({ ...base }, recipe as any) as T;
|
||||
return create({ ...base }, recipeWrapper) as T;
|
||||
}
|
||||
return create(base, recipe as any) as T;
|
||||
return create(base, recipeWrapper) as T;
|
||||
}
|
||||
@@ -92,7 +92,7 @@ async function createDataContext(file: FileHandle): Promise<Data.DataContext> {
|
||||
return {
|
||||
file,
|
||||
header,
|
||||
spacegroup: SpacegroupCell.create(header.spacegroup.number || 'P 1', Vec3.ofArray(header.spacegroup.size), Vec3.scale(Vec3.zero(), Vec3.ofArray(header.spacegroup.angles), Math.PI / 180)),
|
||||
spacegroup: SpacegroupCell.create(header.spacegroup.number, Vec3.ofArray(header.spacegroup.size), Vec3.scale(Vec3.zero(), Vec3.ofArray(header.spacegroup.angles), Math.PI / 180)),
|
||||
dataBox: { a: origin, b: Coords.add(origin, dimensions) },
|
||||
sampling: header.sampling.map((s, i) => createSampling(header, i, dataOffset))
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user