mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 13:30:24 +08:00
Markdown Commands and MVS improvements (#1597)
* add query command to markdown extensions * fix typo * better postprocessing param support in MVS * molstar_mesh/label/line_params
This commit is contained in:
@@ -19,8 +19,9 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
- MolViewSpec extension:
|
||||
- Generic color schemes (`palette` parameter for color_from_* nodes)
|
||||
- Annotation field remapping (`field_remapping` parameter for color_from_* nodes)
|
||||
- Representation node: support custom property `molstar_reprepresentation_params`,
|
||||
- Canvas node: support custom properties `molstar_enable_outline`, `molstar_enable_shadow`, `molstar_enable_ssao`
|
||||
- Representation node: support custom property `molstar_reprepresentation_params`
|
||||
- Primitives node: support custom property `molstar_mesh/label/line_params`
|
||||
- Canvas node: support custom properties `molstar_enable_outline/shadow/ssao`, `molstar_outline/shadow/ssao_params`
|
||||
- `clip` node support for structure and volume representations
|
||||
- `grid_slice` representation support for volumes
|
||||
- Support tethers and background for primitive labels
|
||||
|
||||
@@ -14,12 +14,20 @@ The main use case of this is enriching [MolViewSpec](`https://molstar.org/mol-vi
|
||||
|
||||
Extends Markdown Hyperlink syntax to support expressions of the form `[title](!c1=v1&c2=v2&...)` into an executable command. The command can be executed either on click, mouse enter, or mouse leave.
|
||||
|
||||
Generally, the command should be URL encoded, e.g., `a b` => `a%20b` (in JS, `encodeURIComponent`, in Python `urllib.parse.quote_plus/urlencode`).
|
||||
|
||||
### Built-in Commands
|
||||
|
||||
- `center-camera` - Centers the camera
|
||||
- `apply-snapshot=key` - Loads snapshots with the provided key
|
||||
- `focus-refs=ref1,ref2,...` - On click, focuses nodes with the provided refs
|
||||
- `highlight-refs=ref1,ref2,...` - On mouse over, highlights the provided refs
|
||||
- `query=...&lang=...&action=highlight,focus&focus-radius=...`
|
||||
- `query` is an expression (e.g., `resn HEM` when using PyMol syntax)
|
||||
- (optional) `lang` is one of `mol-script` (default), `pymol`, `vmd`, `jmol`
|
||||
- (optional) `action` is an array of `highlight` (default), `focus` (multiple actions can be specified)
|
||||
- (optional) `focus-radius` is extra distance applied when focusing the selection (default is `3`)
|
||||
- Example: `[HEM](!query%3Dresn%20HEM%26lang%3Dpymol%26action%3Dhighlight%2Cfocus)` highlights or focuses the HEM residue (the command must be URL encoded because it contains spaces and possibly other special characters)
|
||||
|
||||
## Custom Content
|
||||
|
||||
@@ -28,7 +36,7 @@ Extends Markdown Image syntax to support expressions of the form `
|
||||
- `color-palette-name=name` - Renders a gradient with the provided named color palette (see `mol-util/color/lists.ts` for supported color schemes)
|
||||
- `color-palette-colors=color1,color2` - Renders a gradient with the provided colors
|
||||
- `color-palette-width=CCS-value` - Specifies the width of the element, defaults to `150px`
|
||||
- `color-palette-height=CCS-value` - Specified the height of the element, defaults to `0.5em`
|
||||
|
||||
@@ -130,21 +130,26 @@ export function modifyCanvasProps(oldCanvasProps: Canvas3DProps, canvasNode: Mol
|
||||
const backgroundColor = decodeColor(params?.background_color) ?? DefaultCanvasBackgroundColor;
|
||||
|
||||
const outline = !!canvasNode?.custom?.molstar_enable_outline;
|
||||
const outlineParams = canvasNode?.custom?.molstar_outline_params;
|
||||
|
||||
const shadow = !!canvasNode?.custom?.molstar_enable_shadow;
|
||||
const shadowParams = canvasNode?.custom?.molstar_shadow_params;
|
||||
|
||||
const occlusion = !!canvasNode?.custom?.molstar_enable_ssao;
|
||||
const ssaoParams = canvasNode?.custom?.molstar_ssao_params;
|
||||
|
||||
return {
|
||||
...oldCanvasProps,
|
||||
postprocessing: {
|
||||
...oldCanvasProps.postprocessing,
|
||||
outline: outline
|
||||
? { name: 'on', params: ParamDefinition.getDefaultValues(OutlineParams) }
|
||||
? { name: 'on', params: { ...ParamDefinition.getDefaultValues(OutlineParams), ...outlineParams } }
|
||||
: oldCanvasProps.postprocessing.outline,
|
||||
shadow: shadow
|
||||
? { name: 'on', params: ParamDefinition.getDefaultValues(ShadowParams) }
|
||||
? { name: 'on', params: { ...ParamDefinition.getDefaultValues(ShadowParams), ...shadowParams } }
|
||||
: oldCanvasProps.postprocessing.shadow,
|
||||
occlusion: occlusion
|
||||
? { name: 'on', params: ParamDefinition.getDefaultValues(SsaoParams) }
|
||||
? { name: 'on', params: { ...ParamDefinition.getDefaultValues(SsaoParams), ...ssaoParams } }
|
||||
: oldCanvasProps.postprocessing.occlusion,
|
||||
},
|
||||
renderer: {
|
||||
|
||||
@@ -140,11 +140,13 @@ export const MVSBuildPrimitiveShape = MVSTransform({
|
||||
if (params.kind === 'mesh') {
|
||||
if (!hasPrimitiveKind(a.data, 'mesh')) return StateObject.Null;
|
||||
|
||||
const customMeshParams = a.data.node.custom?.molstar_mesh_params;
|
||||
return new SO.Shape.Provider({
|
||||
label,
|
||||
data: context,
|
||||
params: {
|
||||
...PD.withDefaults(Mesh.Params, { alpha: a.data.options?.opacity ?? 1 }),
|
||||
...customMeshParams,
|
||||
...snapshotKey,
|
||||
},
|
||||
getShape: (_, data, __, prev: any) => buildPrimitiveMesh(data, prev?.geometry),
|
||||
@@ -155,6 +157,7 @@ export const MVSBuildPrimitiveShape = MVSTransform({
|
||||
|
||||
const options = a.data.options;
|
||||
const bgColor = options?.label_background_color;
|
||||
const customLabelParams = a.data.node.custom?.molstar_label_params;
|
||||
return new SO.Shape.Provider({
|
||||
label,
|
||||
data: context,
|
||||
@@ -167,6 +170,7 @@ export const MVSBuildPrimitiveShape = MVSTransform({
|
||||
background: isDefined(bgColor),
|
||||
backgroundColor: isDefined(bgColor) ? decodeColor(bgColor) : undefined,
|
||||
}),
|
||||
...customLabelParams,
|
||||
...snapshotKey,
|
||||
},
|
||||
getShape: (_, data, props, prev: any) => buildPrimitiveLabels(data, prev?.geometry, props),
|
||||
@@ -175,11 +179,13 @@ export const MVSBuildPrimitiveShape = MVSTransform({
|
||||
} else if (params.kind === 'lines') {
|
||||
if (!hasPrimitiveKind(a.data, 'line')) return StateObject.Null;
|
||||
|
||||
const customLineParams = a.data.node.custom?.molstar_line_params;
|
||||
return new SO.Shape.Provider({
|
||||
label,
|
||||
data: context,
|
||||
params: {
|
||||
...PD.withDefaults(Lines.Params, { alpha: a.data.options?.opacity ?? 1 }),
|
||||
...customLineParams,
|
||||
...snapshotKey,
|
||||
},
|
||||
getShape: (_, data, __, prev: any) => buildPrimitiveLines(data, prev?.geometry),
|
||||
|
||||
@@ -8,6 +8,8 @@ import { getCellBoundingSphere } from '../../mol-plugin-state/manager/focus-came
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { StateObjectCell } from '../../mol-state';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { Script } from '../../mol-script/script';
|
||||
import { QueryContext, QueryFn, StructureElement, StructureSelection } from '../../mol-model/structure';
|
||||
|
||||
export type MarkdownExtensionEvent = 'click' | 'mouse-enter' | 'mouse-leave';
|
||||
|
||||
@@ -82,6 +84,72 @@ export const BuiltInMarkdownExtension: MarkdownExtension[] = [
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'query',
|
||||
execute: ({ event, args, manager }) => {
|
||||
const expression = args['query'];
|
||||
if (!expression?.length) return;
|
||||
|
||||
// supported languages: mol-script, pymol, vmd, jmol
|
||||
const language = args['lang'] || 'mol-script';
|
||||
// supported actions: highlight, focus
|
||||
const action = parseArray(args['action'] || 'highlight');
|
||||
const focusRadius = parseFloat(args['focus-radius'] || '3');
|
||||
|
||||
if (event === 'mouse-leave') {
|
||||
if (action.includes('highlight')) {
|
||||
manager.plugin.managers.interactivity.lociHighlights.clearHighlights();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let query: QueryFn<StructureSelection>;
|
||||
try {
|
||||
query = Script.toQuery({
|
||||
language: language as Script.Language,
|
||||
expression
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn(`Failed to parse query '${expression}' (${language})`, e);
|
||||
return;
|
||||
}
|
||||
|
||||
const structures = manager.plugin.state.data.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Structure));
|
||||
|
||||
if (event === 'mouse-enter') {
|
||||
if (!action.includes('focus')) {
|
||||
return;
|
||||
}
|
||||
manager.plugin.managers.interactivity.lociHighlights.clearHighlights();
|
||||
for (const structure of structures) {
|
||||
if (!structure.obj?.data) continue;
|
||||
const selection = query(new QueryContext(structure.obj.data));
|
||||
const loci = StructureSelection.toLociWithSourceUnits(selection);
|
||||
manager.plugin.managers.interactivity.lociHighlights.highlight({
|
||||
loci,
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (event === 'click') {
|
||||
if (!action.includes('focus')) {
|
||||
return;
|
||||
}
|
||||
const spheres = structures.map(s => {
|
||||
if (!s.obj?.data) return undefined;
|
||||
const selection = query(new QueryContext(s.obj.data));
|
||||
if (StructureSelection.isEmpty(selection)) return;
|
||||
|
||||
const loci = StructureSelection.toLociWithSourceUnits(selection);
|
||||
return StructureElement.Loci.getBoundary(loci).sphere;
|
||||
}).filter(s => !!s);
|
||||
|
||||
if (spheres.length) {
|
||||
manager.plugin.managers.camera.focusSpheres(spheres, s => s, { extraRadius: focusRadius });
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export class MarkdownExtensionManager {
|
||||
|
||||
Reference in New Issue
Block a user