mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 13:30:24 +08:00
Viewer improvements (#1725)
* separate exxtensions file * subscribe method on the Viewer object * export lib * Add viewer.structureInteraction * MVS tryGetPrimitivesFromLoci util * param select indicator * tweaks * mapped control icon * docs and tweaks * viewer color themes * tweak * typo * pr feedback
This commit is contained in:
11
CHANGELOG.md
11
CHANGELOG.md
@@ -15,6 +15,17 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
- Fix program not compiled for sync picking
|
||||
- Fix missing `gl.flush` for async picking (needed for Safari)
|
||||
- Add Residue Charge color scheme (#1722)
|
||||
- Add dropdown indicator for mapped parameter definitions and adjust "more options" icon
|
||||
- MolViewSpec extension
|
||||
- Add `tryGetPrimitivesFromLoci` that makes it easier to access primitive element data from hover/click interactions
|
||||
- Viewer app
|
||||
- Move viewer extensions, options, and presets to a separate file
|
||||
- Add `molstar.lib` export providing access to a wide range of functionality previously not available from the compiled bundle
|
||||
- Add `Viewer.subscribe` method that keeps track of subscribed plugin events and disposes them together with the parent viewer
|
||||
- Add `Viewer.structureInteractivity` that makes it easy to highlight/select elements on the loaded structure
|
||||
- Add `viewportBackgroundColor` and `viewportFocusBehavior` options
|
||||
- Add `mvs.html` example to showcase the new functionality combined with MolViewSpec
|
||||
- Add dark and blue color theme support (import `theme/dark.css` or `theme/blue.css` instead of the default `molstar.css`)
|
||||
- Fix `flipSided` for meshes
|
||||
|
||||
## [v5.4.2] - 2025-12-07
|
||||
|
||||
@@ -15,10 +15,24 @@ There are 4 basic ways of instantiating the Mol* plugin.
|
||||
|
||||
## ``Viewer`` wrapper
|
||||
|
||||
- The most basic usage is to use the ``Viewer`` wrapper. This is best suited for use cases that do not require much custom behavior and are mostly about just displaying a structure.
|
||||
- See ``Viewer`` class is defined in [src/apps/viewer/app.ts](https://github.com/molstar/molstar/blob/master/src/apps/viewer/app.ts) for available methods and options.
|
||||
- The most basic usage is to use the ``Viewer`` wrapper. This is best suited for use cases that do not require custom behavior and are mostly about just displaying a structure.
|
||||
- See ``Viewer`` class is defined in [src/apps/viewer/app.ts](https://github.com/molstar/molstar/blob/master/src/apps/viewer/app.ts) for available methods
|
||||
- See [options.ts](https://github.com/molstar/molstar/blob/master/src/apps/viewer/options.ts) for available plugin options
|
||||
- See [embedded.html](https://github.com/molstar/molstar/blob/master/src/apps/viewer/embedded.html) and [mvs.html](https://github.com/molstar/molstar/blob/master/src/apps/viewer/mvs.html) for example usage
|
||||
- Importing `molstar.js` will expose `molstar.lib` namespace that allow accessing various functionality without a bundler such as WebPack or esbuild. See the `mvs` example above for basic usage.
|
||||
- Alternative color themes can be used by importing `theme/dark.css` (or `light/blue`) instead of `molstar.css`
|
||||
|
||||
Example usage without using WebPack:
|
||||
### molstar.js and molstar.css sources
|
||||
|
||||
- Download `molstar` NPM package and use the files from `build/viewer` diractory
|
||||
- Use `jsdelivr` CDN
|
||||
- `<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/molstar@latest/build/viewer/molstar.js" />`
|
||||
- `<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/molstar@latest/build/viewer/molstar.css" />`
|
||||
- `@latest` can be replaced by a specific Mol* version, e.g., `@5.4.2`
|
||||
- Clone & build the GitHub repository
|
||||
- This option allows for quite straightforward extension customization, e.g., not including movie export, which reduces the bundle size by ~0.5MB
|
||||
|
||||
### Example
|
||||
|
||||
```HTML
|
||||
<style>
|
||||
@@ -35,7 +49,7 @@ Example usage without using WebPack:
|
||||
- the folder build/viewer after cloning and building the molstar package
|
||||
- from the build/viewer folder in the Mol* NPM package
|
||||
-->
|
||||
<link rel="stylesheet" type="text/css" href="molstar.css" />
|
||||
<link rel="stylesheet" type="text/css" href="./molstar.css" />
|
||||
<script type="text/javascript" src="./molstar.js"></script>
|
||||
|
||||
<div id="app"></div>
|
||||
@@ -62,13 +76,15 @@ Example usage without using WebPack:
|
||||
</script>
|
||||
```
|
||||
|
||||
When using WebPack (or possibly other build tool) with the Mol* NPM package installed, the viewer class can be imported using
|
||||
### Using WebPack/esbuild/...
|
||||
|
||||
When using WebPack (or other bundler) with the Mol* NPM package installed, the viewer class can be imported using
|
||||
|
||||
```ts
|
||||
import { Viewer } from 'molstar/build/viewer/molstar'
|
||||
import { Viewer } from 'molstar/lib/apps/viewer/app'
|
||||
|
||||
function initViewer(target: string | HTMLElement) {
|
||||
return new Viewer(target, { /* options */})
|
||||
return Viewer.create(target, { /* options */}) // returns a Promise
|
||||
}
|
||||
```
|
||||
|
||||
@@ -139,6 +155,8 @@ export function MolStarWrapper() {
|
||||
// In debug mode of react's strict mode, this code will
|
||||
// be called twice in a row, which might result in unexpected behavior.
|
||||
useEffect(() => {
|
||||
// By default, react will call each useEffect twice if using Strict mode in
|
||||
// debug build, it is recommended to disable strict mode for this reason if possible
|
||||
async function init() {
|
||||
window.molstar = await createPluginUI({
|
||||
target: parent.current as HTMLDivElement,
|
||||
|
||||
@@ -13,7 +13,7 @@ import * as os from 'os';
|
||||
|
||||
const Apps = [
|
||||
// Apps
|
||||
{ kind: 'app', name: 'viewer' },
|
||||
{ kind: 'app', name: 'viewer', themes: ['light', 'dark', 'blue'] },
|
||||
{ kind: 'app', name: 'docking-viewer' },
|
||||
{ kind: 'app', name: 'mesoscale-explorer' },
|
||||
{ kind: 'app', name: 'mvs-stories', globalName: 'mvsStories', filename: 'mvs-stories.js' },
|
||||
@@ -132,7 +132,6 @@ 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],
|
||||
@@ -173,6 +172,41 @@ async function createBundle(app) {
|
||||
if (!isProduction) await ctx.watch();
|
||||
}
|
||||
|
||||
async function createTheme(appName, themeName) {
|
||||
// const { prefix, entry, outfile } = getPaths(app);
|
||||
|
||||
const ctx = await esbuild.context({
|
||||
entryPoints: [resolveEntryPath(`./src/apps/${appName}/theme/${themeName}.ts`)],
|
||||
tsconfig: './tsconfig.json',
|
||||
bundle: true,
|
||||
minify: isProduction,
|
||||
minifyIdentifiers: false,
|
||||
sourcemap: false,
|
||||
outfile: `./build/${appName}/theme/${themeName}.js`,
|
||||
plugins: [
|
||||
// fileLoaderPlugin({ out: prefix }),
|
||||
sassPlugin({
|
||||
type: 'css',
|
||||
silenceDeprecations: ['import'],
|
||||
logger: {
|
||||
warn: (msg) => console.warn(msg),
|
||||
debug: () => { },
|
||||
}
|
||||
}),
|
||||
],
|
||||
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),
|
||||
},
|
||||
});
|
||||
|
||||
await ctx.rebuild();
|
||||
|
||||
if (!isProduction) await ctx.watch();
|
||||
}
|
||||
|
||||
function findBrowserTests(names) {
|
||||
const dir = path.resolve('./src', 'tests', 'browser');
|
||||
let files = fs.readdirSync(dir).filter(file => file.endsWith('.ts')).map(file => file.replace('.ts', ''));
|
||||
@@ -230,6 +264,7 @@ const args = argParser.parse_args();
|
||||
const isProduction = !!args.prd;
|
||||
const includeSourceMap = !args.no_src_map;
|
||||
|
||||
const NODE_ENV_PRD = isProduction || process.env.NODE_ENV === 'production';
|
||||
const VERSION = isProduction ? JSON.parse(fs.readFileSync('./package.json', 'utf8')).version : '(dev build)';
|
||||
const TIMESTAMP = Date.now();
|
||||
|
||||
@@ -261,7 +296,14 @@ async function main() {
|
||||
const promises = [];
|
||||
console.log(isProduction ? 'Building apps...' : 'Initial build...');
|
||||
|
||||
for (const app of apps) promises.push(createBundle(app));
|
||||
for (const app of apps) {
|
||||
promises.push(createBundle(app));
|
||||
if (app.themes) {
|
||||
for (const theme of app.themes) {
|
||||
promises.push(createTheme(app.name, theme));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const example of examples) promises.push(createBundle(example));
|
||||
for (const browserTest of browserTests) promises.push(createBundle(browserTest));
|
||||
|
||||
|
||||
@@ -7,34 +7,20 @@
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*/
|
||||
|
||||
import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
|
||||
import { Backgrounds } from '../../extensions/backgrounds';
|
||||
import { DnatcoNtCs } from '../../extensions/dnatco';
|
||||
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
|
||||
import { GeometryExport } from '../../extensions/geo-export';
|
||||
import { MAQualityAssessment, MAQualityAssessmentConfig, QualityAssessmentPLDDTPreset, QualityAssessmentQmeanPreset } from '../../extensions/model-archive/quality-assessment/behavior';
|
||||
import { QualityAssessment } from '../../extensions/model-archive/quality-assessment/prop';
|
||||
import { ModelExport } from '../../extensions/model-export';
|
||||
import { Mp4Export } from '../../extensions/mp4-export';
|
||||
import { MolViewSpec } from '../../extensions/mvs/behavior';
|
||||
import { AssemblySymmetryConfig } from '../../extensions/assembly-symmetry';
|
||||
import { loadMVSData, loadMVSX } from '../../extensions/mvs/components/formats';
|
||||
import { loadMVS, MolstarLoadingExtension } from '../../extensions/mvs/load';
|
||||
import { MVSData } from '../../extensions/mvs/mvs-data';
|
||||
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
|
||||
import { RCSBValidationReport } from '../../extensions/rcsb';
|
||||
import { AssemblySymmetry, AssemblySymmetryConfig } from '../../extensions/assembly-symmetry';
|
||||
import { SbNcbrPartialCharges, SbNcbrPartialChargesPreset, SbNcbrPartialChargesPropertyProvider, SbNcbrTunnels } from '../../extensions/sb-ncbr';
|
||||
import { wwPDBChemicalComponentDictionary } from '../../extensions/wwpdb/ccd/behavior';
|
||||
import { wwPDBStructConnExtensionFunctions } from '../../extensions/wwpdb/struct-conn';
|
||||
import { ZenodoImport } from '../../extensions/zenodo';
|
||||
import { SaccharideCompIdMapType } from '../../mol-model/structure/structure/carbohydrates/constants';
|
||||
import { StringLike } from '../../mol-io/common/string-like';
|
||||
import { Structure, StructureElement, StructureSelection } from '../../mol-model/structure';
|
||||
import { Volume } from '../../mol-model/volume';
|
||||
import { OpenFiles } from '../../mol-plugin-state/actions/file';
|
||||
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
|
||||
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
|
||||
import { PresetTrajectoryHierarchy } from '../../mol-plugin-state/builder/structure/hierarchy-preset';
|
||||
import { PresetStructureRepresentations, StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { PluginComponent } from '../../mol-plugin-state/component';
|
||||
import { BuiltInCoordinatesFormat } from '../../mol-plugin-state/formats/coordinates';
|
||||
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
|
||||
import { BuiltInTopologyFormat } from '../../mol-plugin-state/formats/topology';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { BuildInVolumeFormat } from '../../mol-plugin-state/formats/volume';
|
||||
@@ -42,98 +28,37 @@ import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { TrajectoryFromModelAndCoordinates } from '../../mol-plugin-state/transforms/model';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { createPluginUI } from '../../mol-plugin-ui';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { renderReact18 } from '../../mol-plugin-ui/react18';
|
||||
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginBehaviors } from '../../mol-plugin/behavior';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginConfig, PluginConfigItem } from '../../mol-plugin/config';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { PluginState } from '../../mol-plugin/state';
|
||||
import { StateObjectRef, StateObjectSelector } from '../../mol-state';
|
||||
import { MolScriptBuilder } from '../../mol-script/language/builder';
|
||||
import { Expression } from '../../mol-script/language/expression';
|
||||
import { Script } from '../../mol-script/script';
|
||||
import { StateObjectSelector } from '../../mol-state';
|
||||
import { Task } from '../../mol-task';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import '../../mol-util/polyfill';
|
||||
import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
import { OpenFiles } from '../../mol-plugin-state/actions/file';
|
||||
import { StringLike } from '../../mol-io/common/string-like';
|
||||
import { ExtensionMap } from './extensions';
|
||||
import { DefaultViewerOptions, ViewerOptions } from './options';
|
||||
|
||||
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
|
||||
export { consoleStats, setDebugMode, setProductionMode, setTimingMode, isProductionMode, isDebugMode, isTimingMode } from '../../mol-util/debug';
|
||||
export { consoleStats, isDebugMode, isProductionMode, isTimingMode, setDebugMode, setProductionMode, setTimingMode } from '../../mol-util/debug';
|
||||
|
||||
const CustomFormats = [
|
||||
['g3d', G3dProvider] as const
|
||||
];
|
||||
|
||||
export const ExtensionMap = {
|
||||
'backgrounds': PluginSpec.Behavior(Backgrounds),
|
||||
'dnatco-ntcs': PluginSpec.Behavior(DnatcoNtCs),
|
||||
'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),
|
||||
'assembly-symmetry': PluginSpec.Behavior(AssemblySymmetry),
|
||||
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
|
||||
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
|
||||
'g3d': PluginSpec.Behavior(G3DFormat),
|
||||
'model-export': PluginSpec.Behavior(ModelExport),
|
||||
'mp4-export': PluginSpec.Behavior(Mp4Export),
|
||||
'geo-export': PluginSpec.Behavior(GeometryExport),
|
||||
'ma-quality-assessment': PluginSpec.Behavior(MAQualityAssessment),
|
||||
'zenodo-import': PluginSpec.Behavior(ZenodoImport),
|
||||
'sb-ncbr-partial-charges': PluginSpec.Behavior(SbNcbrPartialCharges),
|
||||
'wwpdb-chemical-component-dictionary': PluginSpec.Behavior(wwPDBChemicalComponentDictionary),
|
||||
'mvs': PluginSpec.Behavior(MolViewSpec),
|
||||
'tunnels': PluginSpec.Behavior(SbNcbrTunnels),
|
||||
};
|
||||
|
||||
const DefaultViewerOptions = {
|
||||
customFormats: CustomFormats as [string, DataFormatProvider][],
|
||||
extensions: ObjectKeys(ExtensionMap),
|
||||
disabledExtensions: [] as string[],
|
||||
layoutIsExpanded: true,
|
||||
layoutShowControls: true,
|
||||
layoutShowRemoteState: true,
|
||||
layoutControlsDisplay: 'reactive' as PluginLayoutControlsDisplay,
|
||||
layoutShowSequence: true,
|
||||
layoutShowLog: true,
|
||||
layoutShowLeftPanel: true,
|
||||
collapseLeftPanel: false,
|
||||
collapseRightPanel: false,
|
||||
disableAntialiasing: PluginConfig.General.DisableAntialiasing.defaultValue,
|
||||
pixelScale: PluginConfig.General.PixelScale.defaultValue,
|
||||
pickScale: PluginConfig.General.PickScale.defaultValue,
|
||||
transparency: PluginConfig.General.Transparency.defaultValue,
|
||||
preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue,
|
||||
allowMajorPerformanceCaveat: PluginConfig.General.AllowMajorPerformanceCaveat.defaultValue,
|
||||
powerPreference: PluginConfig.General.PowerPreference.defaultValue,
|
||||
resolutionMode: PluginConfig.General.ResolutionMode.defaultValue,
|
||||
illumination: false,
|
||||
|
||||
viewportShowReset: PluginConfig.Viewport.ShowReset.defaultValue,
|
||||
viewportShowScreenshotControls: PluginConfig.Viewport.ShowScreenshotControls.defaultValue,
|
||||
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
|
||||
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
|
||||
viewportShowToggleFullscreen: PluginConfig.Viewport.ShowToggleFullscreen.defaultValue,
|
||||
viewportShowSettings: PluginConfig.Viewport.ShowSettings.defaultValue,
|
||||
viewportShowSelectionMode: PluginConfig.Viewport.ShowSelectionMode.defaultValue,
|
||||
viewportShowAnimation: PluginConfig.Viewport.ShowAnimation.defaultValue,
|
||||
viewportShowTrajectoryControls: PluginConfig.Viewport.ShowTrajectoryControls.defaultValue,
|
||||
pluginStateServer: PluginConfig.State.DefaultServer.defaultValue,
|
||||
volumeStreamingServer: PluginConfig.VolumeStreaming.DefaultServer.defaultValue,
|
||||
volumeStreamingDisabled: !PluginConfig.VolumeStreaming.Enabled.defaultValue,
|
||||
pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue,
|
||||
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
|
||||
saccharideCompIdMapType: 'default' as SaccharideCompIdMapType,
|
||||
rcsbAssemblySymmetryDefaultServerType: AssemblySymmetryConfig.DefaultServerType.defaultValue,
|
||||
rcsbAssemblySymmetryDefaultServerUrl: AssemblySymmetryConfig.DefaultServerUrl.defaultValue,
|
||||
rcsbAssemblySymmetryApplyColors: AssemblySymmetryConfig.ApplyColors.defaultValue,
|
||||
|
||||
config: [] as [PluginConfigItem, any][],
|
||||
};
|
||||
type ViewerOptions = typeof DefaultViewerOptions;
|
||||
import '../../mol-util/polyfill';
|
||||
import { ViewerAutoPreset } from './presets';
|
||||
import { decodeColor } from '../../mol-util/color/utils';
|
||||
|
||||
export class Viewer {
|
||||
constructor(public plugin: PluginUIContext) {
|
||||
private _events = new PluginComponent();
|
||||
public readonly plugin: PluginUIContext;
|
||||
|
||||
constructor(plugin: PluginUIContext) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
static async create(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) {
|
||||
@@ -148,11 +73,19 @@ export class Viewer {
|
||||
const defaultSpec = DefaultPluginUISpec();
|
||||
|
||||
const disabledExtension = new Set(o.disabledExtensions ?? []);
|
||||
let baseBehaviors = defaultSpec.behaviors;
|
||||
|
||||
if (o.viewportFocusBehavior === 'disabled') {
|
||||
baseBehaviors = baseBehaviors.filter(b =>
|
||||
b.transformer !== PluginBehaviors.Camera.FocusLoci
|
||||
&& b.transformer !== PluginBehaviors.Representation.FocusLoci
|
||||
);
|
||||
}
|
||||
|
||||
const spec: PluginUISpec = {
|
||||
actions: defaultSpec.actions,
|
||||
behaviors: [
|
||||
...defaultSpec.behaviors,
|
||||
...baseBehaviors,
|
||||
...o.extensions.filter(e => !disabledExtension.has(e)).map(e => ExtensionMap[e]),
|
||||
],
|
||||
animations: [...defaultSpec.animations || []],
|
||||
@@ -228,10 +161,23 @@ export class Viewer {
|
||||
plugin.builders.structure.representation.registerPreset(ViewerAutoPreset);
|
||||
}
|
||||
});
|
||||
|
||||
plugin.canvas3d?.setProps({ illumination: { enabled: o.illumination } });
|
||||
if (o.viewportBackgroundColor) {
|
||||
const backgroundColor = decodeColor(o.viewportBackgroundColor);
|
||||
if (typeof backgroundColor === 'number') {
|
||||
plugin.canvas3d?.setProps({ renderer: { backgroundColor } });
|
||||
}
|
||||
}
|
||||
return new Viewer(plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows subscribing to rxjs observables in the context of the viewer.
|
||||
* All subscriptions will be disposed of when the viewer is destroyed.
|
||||
*/
|
||||
subscribe = this._events.subscribe.bind(this._events);
|
||||
|
||||
setRemoteSnapshot(id: string) {
|
||||
const url = `${this.plugin.config.get(PluginConfig.State.CurrentServer)}/get/${id}`;
|
||||
return PluginCommands.State.Snapshots.Fetch(this.plugin, { url });
|
||||
@@ -567,7 +513,51 @@ export class Viewer {
|
||||
this.plugin.layout.events.updated.next(void 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers structure element selection or highlighting based on the provided
|
||||
* MolScript expression or StructureElement schema.
|
||||
*
|
||||
* If neither `expression` nor `elements` are provided, all selections/highlights
|
||||
* will be cleared based on the specified `action`.
|
||||
*/
|
||||
structureInteractivity({ expression, elements, action, applyGranularity = false, filterStructure }: {
|
||||
expression?: (queryBuilder: typeof MolScriptBuilder) => Expression,
|
||||
elements?: StructureElement.Schema,
|
||||
action: 'highlight' | 'select',
|
||||
applyGranularity?: boolean,
|
||||
filterStructure?: (structure: Structure) => boolean
|
||||
}) {
|
||||
const plugin = this.plugin;
|
||||
|
||||
if (!expression && !elements) {
|
||||
if (action === 'select') {
|
||||
plugin.managers.interactivity.lociSelects.deselectAll();
|
||||
} else if (action === 'highlight') {
|
||||
plugin.managers.interactivity.lociHighlights.clearHighlights();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const structures = this.plugin.state.data.selectQ(Q => Q.rootsOfType(PluginStateObject.Molecule.Structure));
|
||||
for (const s of structures) {
|
||||
if (!s.obj?.data) continue;
|
||||
|
||||
if (filterStructure && !filterStructure(s.obj.data)) continue;
|
||||
|
||||
const loci = expression
|
||||
? StructureSelection.toLociWithSourceUnits(Script.getStructureSelection(expression, s.obj.data))
|
||||
: StructureElement.Schema.toLoci(s.obj.data, elements!);
|
||||
|
||||
if (action === 'select') {
|
||||
plugin.managers.interactivity.lociSelects.select({ loci }, applyGranularity);
|
||||
} else if (action === 'highlight') {
|
||||
plugin.managers.interactivity.lociHighlights.highlight({ loci }, applyGranularity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._events.dispose();
|
||||
this.plugin.dispose();
|
||||
}
|
||||
}
|
||||
@@ -594,44 +584,4 @@ export interface LoadTrajectoryParams {
|
||||
| { kind: 'coordinates-data', data: string | number[] | ArrayBuffer | Uint8Array<ArrayBuffer>, format: BuiltInCoordinatesFormat },
|
||||
coordinatesLabel?: string,
|
||||
preset?: keyof PresetTrajectoryHierarchy
|
||||
}
|
||||
|
||||
export const ViewerAutoPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-viewer-auto',
|
||||
display: {
|
||||
name: 'Automatic (w/ Annotation)', group: 'Annotation',
|
||||
description: 'Show standard automatic representation but colored by quality assessment (if available in the model).'
|
||||
},
|
||||
isApplicable(a) {
|
||||
return (
|
||||
!!a.data.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT')) ||
|
||||
!!a.data.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))
|
||||
);
|
||||
},
|
||||
params: () => StructureRepresentationPresetProvider.CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
const structure = structureCell?.obj?.data;
|
||||
if (!structureCell || !structure) return {};
|
||||
|
||||
if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT'))) {
|
||||
return await QualityAssessmentPLDDTPreset.apply(ref, params, plugin);
|
||||
} else if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))) {
|
||||
return await QualityAssessmentQmeanPreset.apply(ref, params, plugin);
|
||||
} else if (!!structure.models.some(m => SbNcbrPartialChargesPropertyProvider.isApplicable(m))) {
|
||||
return await SbNcbrPartialChargesPreset.apply(ref, params, plugin);
|
||||
} else {
|
||||
return await PresetStructureRepresentations.auto.apply(ref, params, plugin);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const PluginExtensions = {
|
||||
wwPDBStructConn: wwPDBStructConnExtensionFunctions,
|
||||
mvs: { MVSData, loadMVS, loadMVSData },
|
||||
modelArchive: {
|
||||
qualityAssessment: {
|
||||
config: MAQualityAssessmentConfig
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
69
src/apps/viewer/extensions.ts
Normal file
69
src/apps/viewer/extensions.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*/
|
||||
|
||||
import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
|
||||
import { AssemblySymmetry } from '../../extensions/assembly-symmetry';
|
||||
import { Backgrounds } from '../../extensions/backgrounds';
|
||||
import { DnatcoNtCs } from '../../extensions/dnatco';
|
||||
import { G3DFormat } from '../../extensions/g3d/format';
|
||||
import { GeometryExport } from '../../extensions/geo-export';
|
||||
import { MAQualityAssessment, MAQualityAssessmentConfig } from '../../extensions/model-archive/quality-assessment/behavior';
|
||||
import { ModelExport } from '../../extensions/model-export';
|
||||
import { Mp4Export } from '../../extensions/mp4-export';
|
||||
import { loadMVS } from '../../extensions/mvs';
|
||||
import { MolViewSpec } from '../../extensions/mvs/behavior';
|
||||
import { loadMVSData } from '../../extensions/mvs/components/formats';
|
||||
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
|
||||
import { RCSBValidationReport } from '../../extensions/rcsb';
|
||||
import { SbNcbrPartialCharges, SbNcbrTunnels } from '../../extensions/sb-ncbr';
|
||||
import { wwPDBChemicalComponentDictionary } from '../../extensions/wwpdb/ccd/behavior';
|
||||
import { wwPDBStructConnExtensionFunctions } from '../../extensions/wwpdb/struct-conn';
|
||||
import { ZenodoImport } from '../../extensions/zenodo';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { MVSData } from '../../extensions/mvs/mvs-data';
|
||||
import * as MVSUtil from '../../extensions/mvs/util';
|
||||
|
||||
export const ExtensionMap = {
|
||||
// Mol* built-in extensions
|
||||
'mvs': PluginSpec.Behavior(MolViewSpec),
|
||||
'backgrounds': PluginSpec.Behavior(Backgrounds),
|
||||
'model-export': PluginSpec.Behavior(ModelExport),
|
||||
'mp4-export': PluginSpec.Behavior(Mp4Export),
|
||||
'geo-export': PluginSpec.Behavior(GeometryExport),
|
||||
'zenodo-import': PluginSpec.Behavior(ZenodoImport),
|
||||
'wwpdb-chemical-component-dictionary': PluginSpec.Behavior(wwPDBChemicalComponentDictionary),
|
||||
|
||||
// 3rd party extensions
|
||||
'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),
|
||||
'dnatco-ntcs': PluginSpec.Behavior(DnatcoNtCs),
|
||||
'assembly-symmetry': PluginSpec.Behavior(AssemblySymmetry),
|
||||
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
|
||||
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
|
||||
'g3d': PluginSpec.Behavior(G3DFormat), // TODO: consider removing this for Mol* 6.0
|
||||
'ma-quality-assessment': PluginSpec.Behavior(MAQualityAssessment),
|
||||
'sb-ncbr-partial-charges': PluginSpec.Behavior(SbNcbrPartialCharges),
|
||||
'tunnels': PluginSpec.Behavior(SbNcbrTunnels),
|
||||
};
|
||||
|
||||
export const PluginExtensions = {
|
||||
wwPDBStructConn: wwPDBStructConnExtensionFunctions,
|
||||
mvs: {
|
||||
MVSData,
|
||||
createBuilder: MVSData.createBuilder,
|
||||
loadMVS,
|
||||
loadMVSData,
|
||||
util: {
|
||||
...MVSUtil
|
||||
}
|
||||
},
|
||||
modelArchive: {
|
||||
qualityAssessment: {
|
||||
config: MAQualityAssessmentConfig
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,12 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import './mvs.html';
|
||||
import './embedded.html';
|
||||
import './favicon.ico';
|
||||
import './index.html';
|
||||
import '../../mol-plugin-ui/skin/light.scss';
|
||||
export * from './lib';
|
||||
export * from './extensions';
|
||||
export * from './app';
|
||||
export * from './presets';
|
||||
|
||||
58
src/apps/viewer/lib.ts
Normal file
58
src/apps/viewer/lib.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as Structure from '../../mol-model/structure';
|
||||
import { DataLoci, EveryLoci, Loci } from '../../mol-model/loci';
|
||||
import { Volume } from '../../mol-model/volume';
|
||||
import { Shape, ShapeGroup } from '../../mol-model/shape';
|
||||
import * as LinearAlgebra3D from '../../mol-math/linear-algebra/3d';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { PluginBehavior } from '../../mol-plugin/behavior';
|
||||
import { DefaultPluginSpec, PluginSpec } from '../../mol-plugin/spec';
|
||||
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginStateObject, PluginStateTransform } from '../../mol-plugin-state/objects';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { StateActions } from '../../mol-plugin-state/actions';
|
||||
import { PluginExtensions } from './extensions';
|
||||
|
||||
export const lib = {
|
||||
structure: {
|
||||
...Structure,
|
||||
},
|
||||
volume: {
|
||||
Volume,
|
||||
},
|
||||
shape: {
|
||||
Shape,
|
||||
ShapeGroup,
|
||||
},
|
||||
loci: {
|
||||
Loci,
|
||||
DataLoci,
|
||||
EveryLoci,
|
||||
},
|
||||
math: {
|
||||
LinearAlgebra: {
|
||||
...LinearAlgebra3D,
|
||||
}
|
||||
},
|
||||
plugin: {
|
||||
PluginContext,
|
||||
PluginConfig,
|
||||
PluginBehavior,
|
||||
PluginSpec,
|
||||
PluginStateObject,
|
||||
PluginStateTransform,
|
||||
StateTransforms,
|
||||
StateActions,
|
||||
DefaultPluginSpec,
|
||||
DefaultPluginUISpec,
|
||||
},
|
||||
extensions: {
|
||||
...PluginExtensions
|
||||
}
|
||||
};
|
||||
147
src/apps/viewer/mvs.html
Normal file
147
src/apps/viewer/mvs.html
Normal file
@@ -0,0 +1,147 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||||
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||
<title>Mol* Viewer MolViewSpec Example</title>
|
||||
<style>
|
||||
#app {
|
||||
position: absolute;
|
||||
left: 100px;
|
||||
top: 100px;
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
}
|
||||
|
||||
#controls {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: sans-serif;
|
||||
gap: 8px;
|
||||
left: 10px;
|
||||
top: 10px;
|
||||
z-index: 10;
|
||||
background-color: black;
|
||||
padding: 10px;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="theme/dark.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div id="controls">
|
||||
<button onclick="selectResidues()">Select Residues 10-50</button>
|
||||
<button onclick="clearSelection()">Clear Selection</button>
|
||||
<div id="selection-info"></div>
|
||||
</div>
|
||||
<script type="text/javascript" src="molstar.js"></script>
|
||||
<script type="text/javascript">
|
||||
function selectResidues() {
|
||||
viewer.structureInteractivity({
|
||||
elements: { beg_auth_seq_id: 10, end_auth_seq_id: 50 },
|
||||
action: 'select',
|
||||
});
|
||||
}
|
||||
|
||||
function clearSelection() {
|
||||
viewer.structureInteractivity({ action: 'select' });
|
||||
}
|
||||
|
||||
molstar.Viewer.create('app', {
|
||||
layoutIsExpanded: true,
|
||||
layoutShowControls: false,
|
||||
layoutShowRemoteState: false,
|
||||
layoutShowSequence: true,
|
||||
layoutShowLog: false,
|
||||
layoutShowLeftPanel: true,
|
||||
|
||||
viewportShowExpand: true,
|
||||
viewportShowSelectionMode: false,
|
||||
viewportShowAnimation: false,
|
||||
viewportFocusBehavior: 'disabled',
|
||||
viewportBackgroundColor: 'black',
|
||||
|
||||
pdbProvider: 'rcsb',
|
||||
emdbProvider: 'rcsb',
|
||||
}).then(viewer => {
|
||||
// Make the viewer accessible globally for the demo buttons
|
||||
window.viewer = viewer;
|
||||
|
||||
// Build MVS state
|
||||
const builder = molstar.lib.extensions.mvs.createBuilder();
|
||||
const structure = builder
|
||||
.download({ url: 'https://www.ebi.ac.uk/pdbe/entry-files/1cbs.bcif' })
|
||||
.parse({ format: 'bcif' })
|
||||
.modelStructure({});
|
||||
structure
|
||||
.component({ selector: 'polymer' })
|
||||
.representation({ type: 'cartoon' })
|
||||
.color({ color: 'green' });
|
||||
structure
|
||||
.component({ selector: 'ligand' })
|
||||
.representation({ type: 'ball_and_stick' })
|
||||
.color({ color: '#cc3399' });
|
||||
|
||||
builder.canvas({
|
||||
background_color: "black",
|
||||
})
|
||||
|
||||
structure.primitives()
|
||||
.sphere({
|
||||
center: { label_comp_id: 'REA' },
|
||||
radius: 3,
|
||||
custom: { action: 'Action 1' },
|
||||
})
|
||||
.label({
|
||||
text: '1',
|
||||
position: { label_comp_id: 'REA' },
|
||||
label_size: 2.5,
|
||||
label_color: 'blue',
|
||||
});
|
||||
|
||||
structure.primitives()
|
||||
.sphere({
|
||||
center: { label_seq_id: 2 },
|
||||
radius: 3,
|
||||
custom: { action: 'Action 2' },
|
||||
})
|
||||
.label({
|
||||
text: '2',
|
||||
position: { label_seq_id: 2 },
|
||||
label_size: 2.5,
|
||||
label_color: 'blue',
|
||||
});
|
||||
|
||||
const mvsData = builder.getState();
|
||||
|
||||
viewer.loadMvsData(mvsData, 'mvsj');
|
||||
|
||||
// Show current residue interaction
|
||||
viewer.subscribe(viewer.plugin.behaviors.interaction.hover, e => {
|
||||
const infoElement = document.getElementById('selection-info');
|
||||
if (!infoElement) return;
|
||||
|
||||
if (molstar.lib.structure.StructureElement.Loci.is(e.current.loci)) {
|
||||
molstar.lib.structure.StructureElement.Loci.forEachLocation(e.current.loci, location => {
|
||||
const props = molstar.lib.structure.StructureProperties;
|
||||
infoElement.innerText = `Hovered Residue: ${props.chain.label_asym_id(location)} ${props.residue.label_seq_id(location)}`;
|
||||
});
|
||||
} else {
|
||||
infoElement.innerText = '';
|
||||
}
|
||||
});
|
||||
|
||||
// Show clicked primitive action
|
||||
viewer.subscribe(viewer.plugin.behaviors.interaction.click, e => {
|
||||
const nodes = molstar.lib.extensions.mvs.util.tryGetPrimitivesFromLoci(e.current.loci);
|
||||
if (nodes?.length) {
|
||||
alert('Clicked on: ' + (nodes[0].custom?.action || 'unknown'));
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
69
src/apps/viewer/options.ts
Normal file
69
src/apps/viewer/options.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { AssemblySymmetryConfig } from '../../extensions/assembly-symmetry';
|
||||
import { G3dProvider } from '../../extensions/g3d/format';
|
||||
import { SaccharideCompIdMapType } from '../../mol-model/structure/structure/carbohydrates/constants';
|
||||
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
|
||||
import { PluginConfig, PluginConfigItem } from '../../mol-plugin/config';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
import '../../mol-util/polyfill';
|
||||
import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
import { ExtensionMap } from './extensions';
|
||||
|
||||
const CustomFormats: [string, DataFormatProvider][] = [
|
||||
['g3d', G3dProvider] as const
|
||||
];
|
||||
|
||||
export const DefaultViewerOptions = {
|
||||
customFormats: CustomFormats as [string, DataFormatProvider][],
|
||||
extensions: ObjectKeys(ExtensionMap),
|
||||
disabledExtensions: [] as string[],
|
||||
layoutIsExpanded: true,
|
||||
layoutShowControls: true,
|
||||
layoutShowRemoteState: true,
|
||||
layoutControlsDisplay: 'reactive' as PluginLayoutControlsDisplay,
|
||||
layoutShowSequence: true,
|
||||
layoutShowLog: true,
|
||||
layoutShowLeftPanel: true,
|
||||
collapseLeftPanel: false,
|
||||
collapseRightPanel: false,
|
||||
disableAntialiasing: PluginConfig.General.DisableAntialiasing.defaultValue,
|
||||
pixelScale: PluginConfig.General.PixelScale.defaultValue,
|
||||
pickScale: PluginConfig.General.PickScale.defaultValue,
|
||||
transparency: PluginConfig.General.Transparency.defaultValue,
|
||||
preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue,
|
||||
allowMajorPerformanceCaveat: PluginConfig.General.AllowMajorPerformanceCaveat.defaultValue,
|
||||
powerPreference: PluginConfig.General.PowerPreference.defaultValue,
|
||||
resolutionMode: PluginConfig.General.ResolutionMode.defaultValue,
|
||||
illumination: false,
|
||||
|
||||
viewportShowReset: PluginConfig.Viewport.ShowReset.defaultValue,
|
||||
viewportShowScreenshotControls: PluginConfig.Viewport.ShowScreenshotControls.defaultValue,
|
||||
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
|
||||
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
|
||||
viewportShowToggleFullscreen: PluginConfig.Viewport.ShowToggleFullscreen.defaultValue,
|
||||
viewportShowSettings: PluginConfig.Viewport.ShowSettings.defaultValue,
|
||||
viewportShowSelectionMode: PluginConfig.Viewport.ShowSelectionMode.defaultValue,
|
||||
viewportShowAnimation: PluginConfig.Viewport.ShowAnimation.defaultValue,
|
||||
viewportShowTrajectoryControls: PluginConfig.Viewport.ShowTrajectoryControls.defaultValue,
|
||||
viewportFocusBehavior: 'default' as 'default' | 'disabled',
|
||||
viewportBackgroundColor: undefined as string | undefined,
|
||||
|
||||
pluginStateServer: PluginConfig.State.DefaultServer.defaultValue,
|
||||
volumeStreamingServer: PluginConfig.VolumeStreaming.DefaultServer.defaultValue,
|
||||
volumeStreamingDisabled: !PluginConfig.VolumeStreaming.Enabled.defaultValue,
|
||||
pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue,
|
||||
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
|
||||
saccharideCompIdMapType: 'default' as SaccharideCompIdMapType,
|
||||
rcsbAssemblySymmetryDefaultServerType: AssemblySymmetryConfig.DefaultServerType.defaultValue,
|
||||
rcsbAssemblySymmetryDefaultServerUrl: AssemblySymmetryConfig.DefaultServerUrl.defaultValue,
|
||||
rcsbAssemblySymmetryApplyColors: AssemblySymmetryConfig.ApplyColors.defaultValue,
|
||||
|
||||
config: [] as [PluginConfigItem, any][],
|
||||
};
|
||||
export type ViewerOptions = typeof DefaultViewerOptions;
|
||||
42
src/apps/viewer/presets.ts
Normal file
42
src/apps/viewer/presets.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2025 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>
|
||||
*/
|
||||
|
||||
import { QualityAssessmentPLDDTPreset, QualityAssessmentQmeanPreset } from '../../extensions/model-archive/quality-assessment/behavior';
|
||||
import { QualityAssessment } from '../../extensions/model-archive/quality-assessment/prop';
|
||||
import { SbNcbrPartialChargesPreset, SbNcbrPartialChargesPropertyProvider } from '../../extensions/sb-ncbr';
|
||||
import { PresetStructureRepresentations, StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { StateObjectRef } from '../../mol-state';
|
||||
|
||||
export const ViewerAutoPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-viewer-auto',
|
||||
display: {
|
||||
name: 'Automatic (w/ Annotation)', group: 'Annotation',
|
||||
description: 'Show standard automatic representation but colored by quality assessment (if available in the model).'
|
||||
},
|
||||
isApplicable(a) {
|
||||
return (
|
||||
!!a.data.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT')) ||
|
||||
!!a.data.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))
|
||||
);
|
||||
},
|
||||
params: () => StructureRepresentationPresetProvider.CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
const structure = structureCell?.obj?.data;
|
||||
if (!structureCell || !structure) return {};
|
||||
|
||||
if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT'))) {
|
||||
return await QualityAssessmentPLDDTPreset.apply(ref, params, plugin);
|
||||
} else if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))) {
|
||||
return await QualityAssessmentQmeanPreset.apply(ref, params, plugin);
|
||||
} else if (!!structure.models.some(m => SbNcbrPartialChargesPropertyProvider.isApplicable(m))) {
|
||||
return await SbNcbrPartialChargesPreset.apply(ref, params, plugin);
|
||||
} else {
|
||||
return await PresetStructureRepresentations.auto.apply(ref, params, plugin);
|
||||
}
|
||||
}
|
||||
});
|
||||
7
src/apps/viewer/theme/blue.ts
Normal file
7
src/apps/viewer/theme/blue.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import '../../../mol-plugin-ui/skin/blue.scss';
|
||||
7
src/apps/viewer/theme/dark.ts
Normal file
7
src/apps/viewer/theme/dark.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import '../../../mol-plugin-ui/skin/dark.scss';
|
||||
7
src/apps/viewer/theme/light.ts
Normal file
7
src/apps/viewer/theme/light.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import '../../../mol-plugin-ui/skin/light.scss';
|
||||
@@ -67,6 +67,12 @@ export function getPrimitiveStructureRefs(primitives: MolstarSubtree<'primitives
|
||||
export class MVSPrimitivesData extends SO.Create<PrimitiveBuilderContext>({ name: 'Primitive Data', typeClass: 'Object' }) { }
|
||||
export class MVSPrimitiveShapes extends SO.Create<{ mesh?: Shape<Mesh>, labels?: Shape<Text> }>({ name: 'Primitive Shapes', typeClass: 'Object' }) { }
|
||||
|
||||
export interface MVSPrimitiveShapeSourceData {
|
||||
kind: 'mvs-primitives',
|
||||
node: MVSNode<'primitives'>,
|
||||
groupToNode: Map<number, MVSNode<'primitive'>>,
|
||||
}
|
||||
|
||||
export type MVSDownloadPrimitiveData = typeof MVSDownloadPrimitiveData
|
||||
export const MVSDownloadPrimitiveData = MVSTransform({
|
||||
name: 'mvs-download-primitive-data',
|
||||
@@ -605,7 +611,7 @@ function buildPrimitiveMesh(context: PrimitiveBuilderContext, prev?: Mesh): Shap
|
||||
kind: 'mvs-primitives',
|
||||
node: context.node,
|
||||
groupToNode: state.groups.groupToNodeMap,
|
||||
},
|
||||
} satisfies MVSPrimitiveShapeSourceData,
|
||||
MeshBuilder.getMesh(meshBuilder),
|
||||
(g) => colors.get(g) as Color ?? color,
|
||||
(g) => 1,
|
||||
@@ -638,7 +644,7 @@ function buildPrimitiveLines(context: PrimitiveBuilderContext, prev?: Lines): Sh
|
||||
kind: 'mvs-primitives',
|
||||
node: context.node,
|
||||
groupToNode: state.groups.groupToNodeMap,
|
||||
},
|
||||
} satisfies MVSPrimitiveShapeSourceData,
|
||||
linesBuilder.getLines(),
|
||||
(g) => colors.get(g) as Color ?? color,
|
||||
(g) => sizes.get(g) ?? 1,
|
||||
@@ -673,7 +679,7 @@ function buildPrimitiveLabels(context: PrimitiveBuilderContext, prev: Text | und
|
||||
kind: 'mvs-primitives',
|
||||
node: context.node,
|
||||
groupToNode: state.groups.groupToNodeMap,
|
||||
},
|
||||
} satisfies MVSPrimitiveShapeSourceData,
|
||||
labelsBuilder.getText(),
|
||||
(g) => colors.get(g) as Color ?? color,
|
||||
(g) => sizes.get(g) ?? 1,
|
||||
|
||||
@@ -4,8 +4,13 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { OrderedSet } from '../../mol-data/int';
|
||||
import { Loci } from '../../mol-model/loci';
|
||||
import { ShapeGroup } from '../../mol-model/shape';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { StateObjectSelector, StateTree } from '../../mol-state';
|
||||
import type { MVSPrimitiveShapeSourceData } from './components/primitives';
|
||||
import type { MVSNode } from './tree/mvs/mvs-tree';
|
||||
|
||||
|
||||
/**
|
||||
@@ -33,4 +38,20 @@ export function createMVSRefMap(plugin: PluginContext) {
|
||||
});
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
export function tryGetPrimitivesFromLoci(loci: Loci | undefined): MVSNode<'primitive'>[] | undefined {
|
||||
if (!ShapeGroup.isLoci(loci)) return undefined;
|
||||
|
||||
const srcData = loci.shape.sourceData as MVSPrimitiveShapeSourceData;
|
||||
if (srcData?.kind !== 'mvs-primitives') return undefined;
|
||||
|
||||
const nodes: MVSNode<'primitive'>[] = [];
|
||||
for (const group of loci.groups) {
|
||||
OrderedSet.forEach(group.ids, id => {
|
||||
const node = srcData.groupToNode.get(id);
|
||||
if (node) nodes.push(node);
|
||||
});
|
||||
}
|
||||
return nodes.length > 0 ? nodes : undefined;
|
||||
}
|
||||
@@ -13,7 +13,7 @@ export class PluginComponent {
|
||||
private _ev: RxEventHelper | undefined;
|
||||
private subs: Subscription[] | undefined = void 0;
|
||||
|
||||
protected subscribe<T>(obs: Observable<T> | undefined, action: (v: T) => void) {
|
||||
subscribe<T>(obs: Observable<T> | undefined, action: (v: T) => void) {
|
||||
if (!obs) return { unsubscribe: () => {} };
|
||||
if (typeof this.subs === 'undefined') this.subs = [];
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import { PluginUIContext } from '../context';
|
||||
import { ActionMenu } from './action-menu';
|
||||
import { ColorOptions, ColorValueOption, CombinedColorControl } from './color';
|
||||
import { Button, ControlGroup, ControlRow, ExpandGroup, IconButton, TextInput, ToggleButton } from './common';
|
||||
import { ArrowDownwardSvg, ArrowDropDownSvg, ArrowRightSvg, ArrowUpwardSvg, BookmarksOutlinedSvg, CheckSvg, ClearSvg, DeleteOutlinedSvg, HelpOutlineSvg, Icon, MoreHorizSvg, WarningSvg } from './icons';
|
||||
import { ArrowDownwardSvg, ArrowDropDownSvg, ArrowRightSvg, ArrowUpwardSvg, BookmarksOutlinedSvg, CheckSvg, ClearSvg, DeleteOutlinedSvg, HelpOutlineSvg, Icon, TuneSvg, WarningSvg } from './icons';
|
||||
import { legendFor } from './legend';
|
||||
import { LineGraphComponent } from './line-graph/line-graph-component';
|
||||
import { Slider, Slider2 } from './slider';
|
||||
@@ -526,7 +526,7 @@ export class SelectControl extends React.PureComponent<ParamProps<PD.Select<stri
|
||||
: void 0;
|
||||
|
||||
return <ToggleButton disabled={this.props.isDisabled} style={{ textAlign, overflow: 'hidden', textOverflow: 'ellipsis' }}
|
||||
label={label} title={label as string} icon={icon} toggle={toggle} isSelected={this.state.showOptions} />;
|
||||
label={label} title={label as string} icon={icon} toggle={toggle} isSelected={this.state.showOptions} className='msp-select-toggle' />;
|
||||
}
|
||||
|
||||
renderAddOn() {
|
||||
@@ -1192,7 +1192,7 @@ export class GroupControl extends React.PureComponent<ParamProps<PD.Group<any>>
|
||||
if (!this.state.isExpanded) {
|
||||
return <div className='msp-mapped-parameter-group'>
|
||||
{ctrl}
|
||||
<IconButton svg={MoreHorizSvg} onClick={this.toggleExpanded} toggleState={this.state.isExpanded} title={`More Options`} />
|
||||
<IconButton svg={TuneSvg} onClick={this.toggleExpanded} toggleState={this.state.isExpanded} title={`More Options`} style={{ opacity: 0.7 }} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -1203,7 +1203,7 @@ export class GroupControl extends React.PureComponent<ParamProps<PD.Group<any>>
|
||||
|
||||
return <div className='msp-mapped-parameter-group'>
|
||||
{ctrl}
|
||||
<IconButton svg={MoreHorizSvg} onClick={this.toggleExpanded} toggleState={this.state.isExpanded} title={`More Options`} />
|
||||
<IconButton svg={TuneSvg} onClick={this.toggleExpanded} toggleState={this.state.isExpanded} title={`More Options`} />
|
||||
<div className='msp-control-offset'>
|
||||
{this.pivotedPresets()}
|
||||
<ParameterControls params={filtered} onEnter={this.props.onEnter} values={this.props.value} onChange={this.onChangeParam} isDisabled={this.props.isDisabled} />
|
||||
@@ -1312,7 +1312,7 @@ export class MappedControl extends React.PureComponent<ParamProps<PD.Mapped<any>
|
||||
if (!this.areParamsEmpty(param.params)) {
|
||||
return <div className='msp-mapped-parameter-group'>
|
||||
{Select}
|
||||
<IconButton svg={MoreHorizSvg} onClick={this.toggleExpanded} toggleState={this.state.isExpanded} title={`${label} Properties`} />
|
||||
<IconButton svg={TuneSvg} onClick={this.toggleExpanded} toggleState={this.state.isExpanded} title={`${label} Properties`} style={{ opacity: this.state.isExpanded ? undefined : 0.7 }} />
|
||||
{this.state.isExpanded && <GroupControl inMapped param={param} value={value.params} name={value.name} onChange={this.onChangeParam} onEnter={this.props.onEnter} isDisabled={this.props.isDisabled} />}
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -286,6 +286,27 @@
|
||||
color: $msp-btn-commit-on-hover-font-color;
|
||||
}
|
||||
|
||||
.msp-select-toggle::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-top: 7px solid $hover-font-color;
|
||||
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.msp-select-toggle:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.msp-btn-action {
|
||||
height: $row-height;
|
||||
line-height: $row-height;
|
||||
|
||||
Reference in New Issue
Block a user