diff --git a/src/apps/viewer/index.ts b/src/apps/viewer/index.ts index dbc813432bbe613347823a411199b9c24c5647fc..676ac2ac421b95b686e131a36e4c595b33c75fa7 100644 --- a/src/apps/viewer/index.ts +++ b/src/apps/viewer/index.ts @@ -88,7 +88,6 @@ async function tryLoadFromUrl(ctx: PluginContext) { format: format as any, isBinary, options: params.source.params.options, - structure: params.source.params.structure, } } })); diff --git a/src/mol-plugin-state/actions/structure.ts b/src/mol-plugin-state/actions/structure.ts index 250f582c50f803d5a37ccf2e00b8005c1aaf61cc..40d89a724bdcceaf81f4cba694d2c8ad3543d768 100644 --- a/src/mol-plugin-state/actions/structure.ts +++ b/src/mol-plugin-state/actions/structure.ts @@ -19,6 +19,7 @@ import { Download, ParsePsf } from '../transforms/data'; import { CoordinatesFromDcd, CustomModelProperties, CustomStructureProperties, TopologyFromPsf, TrajectoryFromModelAndCoordinates } from '../transforms/model'; import { DataFormatProvider, guessCifVariant } from './data-format'; import { applyTrajectoryHierarchyPreset } from '../builder/structure/hierarchy-preset'; +import { PresetStructureReprentations } from '../builder/structure/representation-preset'; // TODO make unitcell creation part of preset @@ -36,7 +37,8 @@ export const MmcifProvider: DataFormatProvider<PluginStateObject.Data.String | P getDefaultBuilder: (ctx: PluginContext, data, options) => { return Task.create('mmCIF default builder', async () => { const trajectory = await ctx.builders.structure.parseTrajectory(data, 'mmcif'); - await applyTrajectoryHierarchyPreset(ctx, trajectory, 'first-model', { showUnitcell: options.visuals, noRepresentation: !options.visuals }); + const representationPreset = options.visuals ? 'auto' : 'empty'; + await applyTrajectoryHierarchyPreset(ctx, trajectory, 'first-model', { showUnitcell: options.visuals, representationPreset }); }) } } @@ -52,7 +54,8 @@ export const PdbProvider: DataFormatProvider<any> = { getDefaultBuilder: (ctx: PluginContext, data, options) => { return Task.create('PDB default builder', async () => { const trajectory = await ctx.builders.structure.parseTrajectory(data, 'pdb'); - await applyTrajectoryHierarchyPreset(ctx, trajectory, 'first-model', { showUnitcell: options.visuals, noRepresentation: !options.visuals }); + const representationPreset = options.visuals ? 'auto' : 'empty'; + await applyTrajectoryHierarchyPreset(ctx, trajectory, 'first-model', { showUnitcell: options.visuals, representationPreset }); }) } } @@ -68,7 +71,8 @@ export const GroProvider: DataFormatProvider<any> = { getDefaultBuilder: (ctx: PluginContext, data, options) => { return Task.create('GRO default builder', async () => { const trajectory = await ctx.builders.structure.parseTrajectory(data, 'gro'); - await applyTrajectoryHierarchyPreset(ctx, trajectory, 'first-model', { showUnitcell: options.visuals, noRepresentation: !options.visuals }); + const representationPreset = options.visuals ? 'auto' : 'empty'; + await applyTrajectoryHierarchyPreset(ctx, trajectory, 'first-model', { showUnitcell: options.visuals, representationPreset }); }) } } @@ -84,7 +88,8 @@ export const Provider3dg: DataFormatProvider<any> = { getDefaultBuilder: (ctx: PluginContext, data, options) => { return Task.create('3DG default builder', async () => { const trajectory = await ctx.builders.structure.parseTrajectory(data, '3dg'); - await applyTrajectoryHierarchyPreset(ctx, trajectory, 'first-model', { showUnitcell: options.visuals, noRepresentation: !options.visuals }); + const representationPreset = options.visuals ? 'auto' : 'empty'; + await applyTrajectoryHierarchyPreset(ctx, trajectory, 'first-model', { showUnitcell: options.visuals, representationPreset }); }) } } @@ -123,59 +128,51 @@ export const DcdProvider: DataFormatProvider<any> = { // -const DownloadModelRepresentationOptions = PD.Group({ +const DownloadModelRepresentationOptions = (plugin: PluginContext) => PD.Group({ type: RootStructureDefinition.getParams(void 0, 'assembly').type, - noRepresentation: PD.Optional(PD.Boolean(false, { description: 'Omit creating default representation.' })) -}, { isExpanded: false }); - -const DownloadStructurePdbIdSourceOptions = PD.Group({ - // supportProps: PD.Optional(PD.Boolean(false)), + representation: PD.Select(PresetStructureReprentations.auto.id, + plugin.builders.structure.representation.getPresets().map(p => [p.id, p.display.name] as any), + { description: 'Which representation preset to use.' }), asTrajectory: PD.Optional(PD.Boolean(false, { description: 'Load all entries into a single trajectory.' })) -}); +}, { isExpanded: false }); export { DownloadStructure }; type DownloadStructure = typeof DownloadStructure const DownloadStructure = StateAction.build({ from: PluginStateObject.Root, display: { name: 'Download Structure', description: 'Load a structure from the provided source and create its representation.' }, - params: { - source: PD.MappedStatic('bcif-static', { - 'pdbe-updated': PD.Group({ - id: PD.Text('1cbs', { label: 'PDB Id(s)', description: 'One or more comma separated PDB ids.' }), - structure: DownloadModelRepresentationOptions, - options: DownloadStructurePdbIdSourceOptions - }, { isFlat: true, label: 'PDBe Updated' }), - 'rcsb': PD.Group({ - id: PD.Text('1tqn', { label: 'PDB Id(s)', description: 'One or more comma separated PDB ids.' }), - structure: DownloadModelRepresentationOptions, - options: DownloadStructurePdbIdSourceOptions - }, { isFlat: true, label: 'RCSB' }), - 'pdb-dev': PD.Group({ - id: PD.Text('PDBDEV_00000001', { label: 'PDBDev Id(s)', description: 'One or more comma separated ids.' }), - structure: DownloadModelRepresentationOptions, - options: DownloadStructurePdbIdSourceOptions - }, { isFlat: true, label: 'PDBDEV' }), - 'bcif-static': PD.Group({ - id: PD.Text('1tqn', { label: 'PDB Id(s)', description: 'One or more comma separated PDB ids.' }), - structure: DownloadModelRepresentationOptions, - options: DownloadStructurePdbIdSourceOptions - }, { isFlat: true, label: 'BinaryCIF (static PDBe Updated)' }), - 'swissmodel': PD.Group({ - id: PD.Text('Q9Y2I8', { label: 'UniProtKB AC(s)', description: 'One or more comma separated ACs.' }), - structure: DownloadModelRepresentationOptions, - options: DownloadStructurePdbIdSourceOptions - }, { isFlat: true, label: 'SWISS-MODEL', description: 'Loads the best homology model or experimental structure' }), - 'url': PD.Group({ - url: PD.Text(''), - format: PD.Select('mmcif', [['mmcif', 'CIF'], ['pdb', 'PDB']] as ['mmcif' | 'pdb', string][]), - isBinary: PD.Boolean(false), - structure: DownloadModelRepresentationOptions, - options: PD.Group({ - // supportProps: PD.Optional(PD.Boolean(false)), - createRepresentation: PD.Optional(PD.Boolean(true)) - }) - }, { isFlat: true, label: 'URL' }) - }) + params: (_, plugin: PluginContext) => { + const options = DownloadModelRepresentationOptions(plugin); + return { + source: PD.MappedStatic('bcif-static', { + 'pdbe-updated': PD.Group({ + id: PD.Text('1cbs', { label: 'PDB Id(s)', description: 'One or more comma separated PDB ids.' }), + options + }, { isFlat: true, label: 'PDBe Updated' }), + 'rcsb': PD.Group({ + id: PD.Text('1tqn', { label: 'PDB Id(s)', description: 'One or more comma separated PDB ids.' }), + options + }, { isFlat: true, label: 'RCSB' }), + 'pdb-dev': PD.Group({ + id: PD.Text('PDBDEV_00000001', { label: 'PDBDev Id(s)', description: 'One or more comma separated ids.' }), + options + }, { isFlat: true, label: 'PDBDEV' }), + 'bcif-static': PD.Group({ + id: PD.Text('1tqn', { label: 'PDB Id(s)', description: 'One or more comma separated PDB ids.' }), + options + }, { isFlat: true, label: 'BinaryCIF (static PDBe Updated)' }), + 'swissmodel': PD.Group({ + id: PD.Text('Q9Y2I8', { label: 'UniProtKB AC(s)', description: 'One or more comma separated ACs.' }), + options + }, { isFlat: true, label: 'SWISS-MODEL', description: 'Loads the best homology model or experimental structure' }), + 'url': PD.Group({ + url: PD.Text(''), + format: PD.Select('mmcif', [['mmcif', 'CIF'], ['pdb', 'PDB']] as ['mmcif' | 'pdb', string][]), + isBinary: PD.Boolean(false), + options + }, { isFlat: true, label: 'URL' }) + }) + } } })(({ params, state }, plugin: PluginContext) => Task.create('Download Structure', async ctx => { plugin.behaviors.layout.leftPanelTabName.next('data'); @@ -227,7 +224,8 @@ const DownloadStructure = StateAction.build({ default: throw new Error(`${(src as any).name} not supported.`); } - const createRepr = !params.source.params.structure.noRepresentation; + const representationPreset: any = params.source.params.options.representation || PresetStructureReprentations.auto.id; + const showUnitcell = representationPreset !== PresetStructureReprentations.empty.id; await state.transaction(async () => { if (downloadParams.length > 0 && asTrajectory) { @@ -237,13 +235,21 @@ const DownloadStructure = StateAction.build({ }, { state: { isGhost: true } }); const trajectory = await plugin.builders.structure.parseTrajectory(blob, { formats: downloadParams.map((_, i) => ({ id: '' + i, format: 'cif' as 'cif' })) }); - await applyTrajectoryHierarchyPreset(plugin, trajectory, 'first-model', { showUnitcell: createRepr, noRepresentation: !createRepr }); + await applyTrajectoryHierarchyPreset(plugin, trajectory, 'first-model', { + structure: src.params.options.type, + showUnitcell, + representationPreset + }); } else { for (const download of downloadParams) { const data = await plugin.builders.data.download(download, { state: { isGhost: true } }); const trajectory = await plugin.builders.structure.parseTrajectory(data, format); - await applyTrajectoryHierarchyPreset(plugin, trajectory, 'first-model', { showUnitcell: createRepr, noRepresentation: !createRepr }); + await applyTrajectoryHierarchyPreset(plugin, trajectory, 'first-model', { + structure: src.params.options.type, + showUnitcell, + representationPreset + }); } } }).runInContext(ctx); diff --git a/src/mol-plugin-state/builder/structure/hierarchy-preset.ts b/src/mol-plugin-state/builder/structure/hierarchy-preset.ts index 282dc193d79b50188138a7ca6a7e311a4824d057..735f6635ffd1214430fdf4ec4b73e63801a196bd 100644 --- a/src/mol-plugin-state/builder/structure/hierarchy-preset.ts +++ b/src/mol-plugin-state/builder/structure/hierarchy-preset.ts @@ -32,7 +32,6 @@ const FirstModelParams = (a: PluginStateObject.Molecule.Trajectory | undefined, model: PD.Optional(PD.Group(StateTransformer.getParamDefinition(StateTransforms.Model.ModelFromTrajectory, a, plugin))), showUnitcell: PD.Optional(PD.Boolean(true)), structure: PD.Optional(RootStructureDefinition.getParams(void 0, 'assembly').type), - noRepresentation: PD.Optional(PD.Boolean(false)), ...CommonParams(a, plugin) }); @@ -44,27 +43,20 @@ const firstModel = TrajectoryHierarchyPresetProvider({ const builder = plugin.builders.structure; const model = await builder.createModel(trajectory, params.model); - const modelProperties = !!params.modelProperties - ? await builder.insertModelProperties(model, params.modelProperties) : void 0; - const modelChildParent = modelProperties || model; + const modelProperties = await builder.insertModelProperties(model, params.modelProperties); const structure = await builder.createStructure(modelProperties || model, params.structure); - const structureProperties = !!params.structureProperties - ? await builder.insertStructureProperties(structure, params.structureProperties) : void 0; - const structureChildParent = structureProperties || structure; + const structureProperties = await builder.insertStructureProperties(structure, params.structureProperties); - const unitcell = params.showUnitcell === void 0 || !!params.showUnitcell ? await builder.tryCreateUnitcell(modelChildParent, undefined, { isHidden: true }) : void 0; - - const representation = !params.noRepresentation - ? await plugin.builders.structure.representation.applyPreset(structureChildParent, params.representationPreset || 'auto') - : void 0; + const unitcell = params.showUnitcell === void 0 || !!params.showUnitcell ? await builder.tryCreateUnitcell(modelProperties, undefined, { isHidden: true }) : void 0; + const representation = await plugin.builders.structure.representation.applyPreset(structureProperties, params.representationPreset || 'auto'); return { - model: modelChildParent, + model: modelProperties, modelRoot: model, modelProperties, unitcell, - structure: structureChildParent, + structure: structureProperties, structureRoot: structure, structureProperties, representation @@ -86,10 +78,13 @@ const allModels = TrajectoryHierarchyPresetProvider({ for (let i = 0; i < tr.length; i++) { const model = await builder.createModel(trajectory, { modelIndex: i }, { isCollapsed: true }); - const structure = await builder.createStructure(model, { name: 'deposited', params: {} }); + const modelProperties = await builder.insertModelProperties(model, params.modelProperties); + const structure = await builder.createStructure(modelProperties || model, { name: 'deposited', params: {} }); + const structureProperties = await builder.insertStructureProperties(structure, params.structureProperties); + models.push(model); structures.push(structure); - await builder.representation.applyPreset(structure, params.representationPreset || 'auto', { globalThemeName: 'model-index' }); + await builder.representation.applyPreset(structureProperties, params.representationPreset || 'auto', { globalThemeName: 'model-index' }); } return { models, structures }; diff --git a/src/mol-plugin-state/builder/structure/representation-preset.ts b/src/mol-plugin-state/builder/structure/representation-preset.ts index aff28abea7701231797dcf3df16369c482cb741e..f665d23f8c6b8e1ef6acc25af36620c268b16720 100644 --- a/src/mol-plugin-state/builder/structure/representation-preset.ts +++ b/src/mol-plugin-state/builder/structure/representation-preset.ts @@ -67,6 +67,14 @@ function reprBuilder(plugin: PluginContext, params: CommonStructureRepresentatio return { update, builder, color, typeParams }; } +const empty = StructureRepresentationPresetProvider({ + id: 'preset-structure-representation-empty', + display: { name: 'Empty', group: 'Preset' }, + async apply(ref, params, plugin) { + return { }; + } +}); + const polymerAndLigand = StructureRepresentationPresetProvider({ id: 'preset-structure-representation-polymer-and-ligand', display: { name: 'Polymer & Ligand', group: 'Preset' }, @@ -228,6 +236,7 @@ export function presetSelectionComponent(plugin: PluginContext, structure: State } export const PresetStructureReprentations = { + empty, auto, 'atomic-detail': atomicDetail, 'polymer-cartoon': polymerCartoon, diff --git a/src/mol-plugin-state/builder/structure/representation.ts b/src/mol-plugin-state/builder/structure/representation.ts index 0d071f75b6ec98a43b5ecb6498f388117929a0bd..a4671507f8f1f2ca633b44243ca8ce97b589ea92 100644 --- a/src/mol-plugin-state/builder/structure/representation.ts +++ b/src/mol-plugin-state/builder/structure/representation.ts @@ -54,6 +54,15 @@ export class StructureRepresentationBuilder { return ret; } + getPresetSelect(s?: PluginStateObject.Molecule.Structure): PD.Select<string> { + const options: [string, string][] = []; + for (const p of this._providers) { + if (s && p.isApplicable && !p.isApplicable(s, this.plugin)) continue; + options.push([p.id, p.display.name]); + } + return PD.Select('auto', options); + } + getPresetsWithOptions(s: PluginStateObject.Molecule.Structure) { const options: [string, string][] = []; const map: { [K in string]: PD.Any } = Object.create(null); diff --git a/src/mol-plugin-state/manager/structure/component.ts b/src/mol-plugin-state/manager/structure/component.ts index 0fbc62afabdb3496173017bd93752a58835b1948..355ed28165b473b719eb939b48d8afeebb052eed 100644 --- a/src/mol-plugin-state/manager/structure/component.ts +++ b/src/mol-plugin-state/manager/structure/component.ts @@ -212,24 +212,41 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage return this.plugin.updateDataState(update, { canUndo: 'Update Representation' }); } - updateRepresentationsTheme<C extends ColorTheme.BuiltIn, S extends SizeTheme.BuiltIn>(components: ReadonlyArray<StructureComponentRef>, params: StructureComponentManager.UpdateThemeParams<C, S>): Promise<any> { - if (components.length === 0) return Promise.resolve(); + /** + * To update theme for all selected structures, use + * plugin.dataTransaction(async () => { + * for (const s of structure.hierarchy.selection.structures) await updateRepresentationsTheme(s.componets, ...); + * }, { canUndo: 'Update Theme' }); + */ + updateRepresentationsTheme<C extends ColorTheme.BuiltIn, S extends SizeTheme.BuiltIn>(components: ReadonlyArray<StructureComponentRef>, params: StructureComponentManager.UpdateThemeParams<C, S>): Promise<any> | undefined + updateRepresentationsTheme<C extends ColorTheme.BuiltIn, S extends SizeTheme.BuiltIn>(components: ReadonlyArray<StructureComponentRef>, params: (c: StructureComponentRef, r: StructureRepresentationRef) => StructureComponentManager.UpdateThemeParams<C, S>): Promise<any> | undefined + updateRepresentationsTheme(components: ReadonlyArray<StructureComponentRef>, paramsOrProvider: StructureComponentManager.UpdateThemeParams<any, any> | ((c: StructureComponentRef, r: StructureRepresentationRef) => StructureComponentManager.UpdateThemeParams<any, any>)) { + if (components.length === 0) return; const update = this.dataState.build(); for (const c of components) { for (const repr of c.representations) { const old = repr.cell.transform.params; - const colorTheme = params.color - ? createStructureColorThemeParams(this.plugin, c.structure.cell.obj?.data, old?.type.name, params.color, params.colorParams) - : void 0; - const sizeTheme = params.color - ? createStructureSizeThemeParams(this.plugin, c.structure.cell.obj?.data, old?.type.name, params.size, params.sizeParams) - : void 0; - update.to(repr.cell).update(prev => { - if (colorTheme) prev.colorTheme = colorTheme; - if (sizeTheme) prev.sizeTheme = sizeTheme; - }); + const params: StructureComponentManager.UpdateThemeParams<any, any> = typeof paramsOrProvider === 'function' ? paramsOrProvider(c, repr) : paramsOrProvider; + + const colorTheme = params.color === 'default' + ? createStructureColorThemeParams(this.plugin, c.structure.cell.obj?.data, old?.type.name) + : params.color + ? createStructureColorThemeParams(this.plugin, c.structure.cell.obj?.data, old?.type.name, params.color, params.colorParams) + : void 0; + const sizeTheme = params.size === 'default' + ? createStructureSizeThemeParams(this.plugin, c.structure.cell.obj?.data, old?.type.name) + : params.color + ? createStructureSizeThemeParams(this.plugin, c.structure.cell.obj?.data, old?.type.name, params.size, params.sizeParams) + : void 0; + + if (colorTheme || sizeTheme) { + update.to(repr.cell).update(prev => { + if (colorTheme) prev.colorTheme = colorTheme; + if (sizeTheme) prev.sizeTheme = sizeTheme; + }); + } } } @@ -392,9 +409,9 @@ namespace StructureComponentManager { /** * this works for any theme name (use 'name as any'), but code completion will break */ - color?: C, + color?: C | 'default', colorParams?: ColorTheme.BuiltInParams<C>, - size?: S, + size?: S | 'default', sizeParams?: SizeTheme.BuiltInParams<S> } } diff --git a/src/mol-plugin-ui/plugin.tsx b/src/mol-plugin-ui/plugin.tsx index 52a07f76691758259f32a9e6c361ae04cfbab37e..dda09d98eb763b86c16a6813283d93ea176201ea 100644 --- a/src/mol-plugin-ui/plugin.tsx +++ b/src/mol-plugin-ui/plugin.tsx @@ -229,7 +229,7 @@ export class Log extends PluginUIComponent<{}, { entries: List<LogEntry> }> { // const actions = cell.status === 'ok' && <StateObjectActionSelect state={current.state} nodeRef={ref} plugin={this.plugin} /> // if (cell.status === 'error') { -// return <> +// return <> // <SectionHeader icon='flow-cascade' title={`${cell.obj?.label || transform.transformer.definition.display.name}`} desc={transform.transformer.definition.display.name} /> // <UpdateTransformControl state={current.state} transform={transform} customHeader='none' /> // {actions} @@ -249,7 +249,7 @@ export class Log extends PluginUIComponent<{}, { entries: List<LogEntry> }> { // </ExpandGroup>); // } -// return <> +// return <> // <SectionHeader icon='flow-cascade' title={`${parent.obj?.label || parent.transform.transformer.definition.display.name}`} desc={parent.transform.transformer.definition.display.name} /> // <UpdateTransformControl state={current.state} transform={parent.transform} customHeader='none' /> // {decorators && <div className='msp-controls-section'>{decorators}</div>} diff --git a/src/mol-plugin-ui/state/tree.tsx b/src/mol-plugin-ui/state/tree.tsx index e4dda875a6fabd5802057e50a76ccee1a263b9b9..f28642886e85722f12a4538aa8fd79ab757a5f06 100644 --- a/src/mol-plugin-ui/state/tree.tsx +++ b/src/mol-plugin-ui/state/tree.tsx @@ -14,6 +14,7 @@ import { ActionMenu } from '../controls/action-menu'; import { ApplyActionControl } from './apply-action'; import { ControlGroup } from '../controls/common'; import { UpdateTransformControl } from './update-transform'; +import { StateTreeSpine } from '../../mol-state/tree/spine'; export class StateTree extends PluginUIComponent<{ state: State }, { showActions: boolean }> { state = { showActions: true }; @@ -254,6 +255,19 @@ class StateTreeNodeLabel extends PluginUIComponent<{ cell: StateObjectCell, dept (item?.value as any)(); } + updates() { + const cell = this.props.cell; + const decoratorChain = StateTreeSpine.getDecoratorChain(cell.parent, cell.transform.ref); + + const decorators = []; + for (let i = decoratorChain.length - 1; i >= 0; i--) { + const d = decoratorChain[i]; + decorators!.push(<UpdateTransformControl key={`${d.transform.transformer.id}-${i}`} state={cell.parent} transform={d.transform} noMargin wrapInExpander />); + } + + return decorators; + } + render() { const cell = this.props.cell; const n = cell.transform; @@ -315,38 +329,11 @@ class StateTreeNodeLabel extends PluginUIComponent<{ cell: StateObjectCell, dept let actions = this.actions; return <div style={{ marginBottom: '1px' }}> {row} - <UpdateTransformControl state={cell.parent} transform={cell.transform} noMargin wrapInExpander /> + {this.updates()} {actions && <ActionMenu items={actions} onSelect={this.selectAction} />} </div> } - // if (this.state.isCurrent) { - // return <> - // {row} - // <StateTreeNodeTransform {...this.props} toggleCollapsed={this.toggleUpdaterObs} /> - // </> - // } - return row; } -} - -// class StateTreeNodeTransform extends PluginUIComponent<{ nodeRef: string, state: State, depth: number, toggleCollapsed?: Observable<any> }> { -// componentDidMount() { -// // this.subscribe(this.plugin.events.state.object.updated, ({ ref, state }) => { -// // if (this.props.nodeRef !== ref || this.props.state !== state) return; -// // this.forceUpdate(); -// // }); -// } - -// render() { -// const ref = this.props.nodeRef; -// const cell = this.props.state.cells.get(ref)!; -// const parent: StateObjectCell | undefined = (cell.sourceRef && this.props.state.cells.get(cell.sourceRef)!) || void 0; - -// if (!parent || parent.status !== 'ok') return null; - -// const transform = cell.transform; -// return <UpdateTransformContol state={this.props.state} transform={transform} initiallyCollapsed={true} toggleCollapsed={this.props.toggleCollapsed} />; -// } -// } \ No newline at end of file +} \ No newline at end of file diff --git a/src/mol-plugin-ui/structure/components.tsx b/src/mol-plugin-ui/structure/components.tsx index bc91366328502ac1eaf7c35441b3fe69187a4912..8c27c2152d545d6066b625ac33115a73cfc86198 100644 --- a/src/mol-plugin-ui/structure/components.tsx +++ b/src/mol-plugin-ui/structure/components.tsx @@ -81,9 +81,7 @@ class ComponentEditorControls extends PurePluginUIComponent<{}, ComponentEditorC } get presetActions() { - const actions = [ - ActionMenu.Item('Clear', null), - ]; + const actions = []; const pivot = this.plugin.managers.structure.component.pivotStructure; const providers = this.plugin.builders.structure.representation.getPresets(pivot?.cell.obj) for (const p of providers) { diff --git a/src/mol-plugin/behavior/dynamic/selection/structure-representation-interaction.ts b/src/mol-plugin/behavior/dynamic/selection/structure-representation-interaction.ts index 4de194b5133036f89bffbe3e65b1b909b91ba61b..eff25ffa4dabb1e51d67512f6f7ae1f6f87cd754 100644 --- a/src/mol-plugin/behavior/dynamic/selection/structure-representation-interaction.ts +++ b/src/mol-plugin/behavior/dynamic/selection/structure-representation-interaction.ts @@ -55,8 +55,6 @@ const StructureRepresentationInteractionParams = (plugin: PluginContext) => { type StructureRepresentationInteractionProps = PD.ValuesFor<ReturnType<typeof StructureRepresentationInteractionParams>> -//PD.Values<typeof StructureRepresentationInteractionParams> - export enum StructureRepresentationInteractionTags { Group = 'structure-interaction-group', ResidueSel = 'structure-interaction-residue-sel', @@ -213,7 +211,7 @@ export class StructureRepresentationInteractionBehavior extends PluginBehavior.W const state = this.plugin.state.data; const builder = state.build(); - const all = StateSelection.Generators.root.subtree(); + const all = StateSelection.Generators.root.subtree(); for (const repr of state.select(all.withTag(StructureRepresentationInteractionTags.ResidueRepr))) { builder.to(repr).update(this.params.focusParams); } diff --git a/src/mol-plugin/util/substructure-parent-helper.ts b/src/mol-plugin/util/substructure-parent-helper.ts index 16e703179e0a1d62fd2be066a0d7131b42c7ff77..10cfdae8a50c3054a9e204bef0140a9151670ef4 100644 --- a/src/mol-plugin/util/substructure-parent-helper.ts +++ b/src/mol-plugin/util/substructure-parent-helper.ts @@ -5,39 +5,91 @@ */ import { Structure } from '../../mol-model/structure'; -import { State, StateObject, StateSelection, StateObjectCell } from '../../mol-state'; +import { State, StateObject, StateSelection, StateObjectCell, StateTransform } from '../../mol-state'; import { PluginContext } from '../context'; import { PluginStateObject } from '../../mol-plugin-state/objects'; +import { arraySetAdd, arraySetRemove } from '../../mol-util/array'; export { SubstructureParentHelper }; class SubstructureParentHelper { + private decorators = new Map<string, string[]>(); private root = new Map<Structure, { ref: string, count: number }>(); private tracked = new Map<string, Structure>(); - /** Returns the root node of given structure if existing */ - get(s: Structure): StateObjectCell<PluginStateObject.Molecule.Structure> | undefined { + /** Returns the root node of given structure if existing, takes decorators into account */ + get(s: Structure, ignoreDecorators = false): StateObjectCell<PluginStateObject.Molecule.Structure> | undefined { const r = this.root.get(s); if (!r) return; - return this.plugin.state.data.cells.get(r.ref); + const decorators = this.decorators.get(r.ref); + if (ignoreDecorators || !decorators) return this.plugin.state.data.cells.get(r.ref); + return this.plugin.state.data.cells.get(this.findDeepestDecorator(r.ref, decorators)); + } + + private findDeepestDecorator(ref: string, decorators: string[]) { + if (decorators.length === 0) return ref; + if (decorators.length === 1) return decorators[0]; + + const cells = this.plugin.state.data.cells; + let depth = 0, ret = ref; + for (const dr of decorators) { + let c = cells.get(dr); + let d = 0; + while (c && c.transform.ref !== StateTransform.RootRef) { + d++; + c = cells.get(c.transform.parent); + } + if (d > depth) { + ret = dr; + depth = d; + } + } + return ret; + } + + private addDecorator(root: string, ref: string) { + if (this.decorators.has(root)) { + arraySetAdd(this.decorators.get(root)!, ref); + } else { + this.decorators.set(root, [ref]); + } + } + + private tryRemoveDecorator(root: string, ref: string) { + if (this.decorators.has(root)) { + const xs = this.decorators.get(root)!; + arraySetRemove(xs, ref); + if (xs.length === 0) this.decorators.delete(root); + } } private addMapping(state: State, ref: string, obj: StateObject) { if (!PluginStateObject.Molecule.Structure.is(obj)) return; - const parent = state.select(StateSelection.Generators.byRef(ref).rootOfType([PluginStateObject.Molecule.Structure]))[0]; this.tracked.set(ref, obj.data); + let parentRef; // if the structure is already present in the tree, do not rewrite the root. if (this.root.has(obj.data)) { - this.root.get(obj.data)!.count++; - return; + const e = this.root.get(obj.data)!; + parentRef = e.ref; + e.count++; + } else { + const parent = state.select(StateSelection.Generators.byRef(ref).rootOfType([PluginStateObject.Molecule.Structure]))[0]; + + if (!parent) { + this.root.set(obj.data, { ref, count: 1 }); + } else { + parentRef = parent.transform.ref; + this.root.set(obj.data, { ref: parentRef, count: 1 }); + } } - if (!parent) { - this.root.set(obj.data, { ref, count : 1 }); - } else { - this.root.set(obj.data, { ref: parent.transform.ref, count: 1 }); + if (!parentRef) return; + + const cell = state.cells.get(ref); + if (cell?.transform.isDecorator) { + this.addDecorator(parentRef, ref); } } @@ -48,6 +100,9 @@ class SubstructureParentHelper { this.tracked.delete(ref); const root = this.root.get(s)!; + + this.tryRemoveDecorator(root.ref, ref); + if (root.count > 1) { root.count--; } else {