Fix Markdown Commands query focus (#1626)

This commit is contained in:
David Sehnal
2025-08-24 13:18:36 +02:00
committed by GitHub
parent 13cf6613a6
commit b3e1e2900b
4 changed files with 28 additions and 16 deletions

View File

@@ -23,11 +23,11 @@ Generally, the command should be URL encoded, e.g., `a b` => `a%20b` (in JS, `en
- `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)
- `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=resn%20HEM%26lang=pymol&action=highlight,focus)` highlights or focuses the HEM residue (the query must be URL encoded because it contains spaces and possibly other special characters)
- `play-audio=src`, `toggle-audio[=str]`, `stop-audio`, `pause-audio` - Audio playback support
## Custom Content
@@ -37,11 +37,11 @@ Extends Markdown Image syntax to support expressions of the form `![alt](!c1=v1&
### Built-in Custom Content
- `color-swatch=color` - Renders a box with the provided color
- Color palettes:
- `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`
- `color-palette-discrete` - Renders discrete color list instead of interpolating
- `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`
- `color-palette-discrete` - Renders discrete color list instead of interpolating
## Example

View File

@@ -13,7 +13,7 @@ import { Loci } from '../../../mol-model/loci';
import { Structure } from '../../../mol-model/structure';
import { PluginContext } from '../../../mol-plugin/context';
import { PluginState } from '../../../mol-plugin/state';
import { StateObject, StateTransform } from '../../../mol-state';
import { StateObjectCell, StateSelection, StateTransform } from '../../../mol-state';
import { PluginStateObject } from '../../objects';
@@ -65,7 +65,7 @@ export function getCellBoundingSphere(plugin: PluginContext, cellRef: StateTrans
/** Push bounding spheres within cell `cellRef` to `out`. If a cell does not define bounding spheres, collect bounding spheres from subtree. */
function collectCellBoundingSpheres(out: Sphere3D[], plugin: PluginContext, cellRef: StateTransform.Ref): Sphere3D[] {
const cell = plugin.state.data.cells.get(cellRef);
const spheres = getStateObjectBoundingSpheres(cell?.obj);
const spheres = getStateObjectBoundingSpheres(plugin, cell);
if (spheres) {
out.push(...spheres);
} else {
@@ -76,14 +76,17 @@ function collectCellBoundingSpheres(out: Sphere3D[], plugin: PluginContext, cell
}
/** Return a set of bounding spheres of a plugin state object. Return `undefined` if this plugin state object type does not define bounding spheres. */
function getStateObjectBoundingSpheres(obj: StateObject | undefined): Sphere3D[] | undefined {
function getStateObjectBoundingSpheres(plugin: PluginContext, cell: StateObjectCell | undefined): Sphere3D[] | undefined {
const obj = cell?.obj;
if (!obj) return undefined;
if (!obj.data) {
console.warn('Focus: no data');
return undefined;
}
if (obj.data instanceof Structure) {
const sphere = Loci.getBoundingSphere(Structure.Loci(obj.data));
const decorated = StateSelection.getDecorated<PluginStateObject.Molecule.Structure>(plugin.state.data, cell.transform.ref);
const data = decorated?.obj?.data ?? obj?.data;
const sphere = Loci.getBoundingSphere(Structure.Loci(data));
return sphere ? [sphere] : [];
} else if (PluginStateObject.isRepresentation3D(obj)) {
const out: Sphere3D[] = [];

View File

@@ -6,7 +6,7 @@
import { getCellBoundingSphere } from '../../mol-plugin-state/manager/focus-camera/focus-object';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { StateObjectCell } from '../../mol-state';
import { StateObjectCell, StateSelection } from '../../mol-state';
import { PluginContext } from '../../mol-plugin/context';
import { Script } from '../../mol-script/script';
import { QueryContext, QueryFn, StructureElement, StructureSelection } from '../../mol-model/structure';
@@ -136,7 +136,8 @@ export const BuiltInMarkdownExtension: MarkdownExtension[] = [
if (!action.includes('focus')) {
return;
}
const spheres = structures.map(s => {
const decorated = structures.map(s => StateSelection.getDecorated<PluginStateObject.Molecule.Structure>(manager.plugin.state.data, s.transform.ref));
const spheres = decorated.map(s => {
if (!s.obj?.data) return undefined;
const selection = query(new QueryContext(s.obj.data));
if (StructureSelection.isEmpty(selection)) return;

View File

@@ -375,6 +375,14 @@ namespace StateSelection {
const first = children.first();
if (first && state.transforms.get(first).transformer.definition.isDecorator) return tryFindDecorator(state, first, transformer);
}
export function getDecorated<T extends StateObject>(state: State, root: StateTransform.Ref): StateObjectCell<T> {
const children = state.tree.children.get(root);
if (children.size !== 1) return state.cells.get(root) as any;
const first = children.first();
if (first && state.transforms.get(first).transformer.definition.isDecorator) return getDecorated(state, first);
return state.cells.get(root) as any;
}
}
export { StateSelection };