浏览代码

mol-plugin-state: StructureHierarchy fix

David Sehnal 5 年之前
父节点
当前提交
7bc91d7e99

+ 0 - 58
src/mol-plugin-state/manager/data.ts

@@ -1,58 +0,0 @@
-/**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { StateTransformer, StateTransform, StateObjectSelector, StateObjectCell } from '../../mol-state';
-import { PluginContext } from '../../mol-plugin/context';
-import { Download, ReadFile } from '../transforms/data';
-import { getFileInfo } from '../../mol-util/file-info';
-import { DataFormatProvider } from './data/provider';
-import { BuiltInDataFormats } from './data/formats';
-import { objectForEach } from '../../mol-util/object';
-
-export class DataManager {
-    readonly formats: DataFormatProvider[] = [];
-
-    addFormat(p: DataFormatProvider) {
-        this.formats.push(p);
-    }
-
-    get dataState() {
-        return this.plugin.state.dataState;
-    }
-
-    async download(params: StateTransformer.Params<Download>, options?: Partial<StateTransform.Options>) {
-        const data = this.dataState.build().toRoot().apply(Download, params, options);
-        await this.plugin.runTask(this.dataState.updateTree(data));
-        return { data: data.selector };
-    }
-
-    async readFile(params: StateTransformer.Params<ReadFile>, options?: Partial<StateTransform.Options>) {
-        const data = this.dataState.build().toRoot().apply(ReadFile, params, options);
-        const fileInfo = getFileInfo(params.file);
-        await this.plugin.runTask(this.dataState.updateTree(data));
-        return { data: data.selector, fileInfo };
-    }
-
-    async parse<K extends keyof BuiltInDataFormats, P extends BuiltInDataFormats[K]>(provider: K, data: StateObjectSelector<DataFormatProvider.Data<P>> | StateObjectCell | string, params?: DataFormatProvider.Params<P>): Promise<DataFormatProvider.Ret<P>>
-    async parse<P extends DataFormatProvider>(provider: P, data: StateObjectSelector<DataFormatProvider.Data<P>> | StateObjectCell | string, params?: DataFormatProvider.Params<P>): Promise<DataFormatProvider.Ret<P>>
-    async parse<P extends DataFormatProvider>(providerOrBuildIn: P | string, data: StateObjectSelector<DataFormatProvider.Data<P>> | StateObjectCell | StateTransform.Ref, params?: DataFormatProvider.Params<P>) {
-        const provider: P = typeof providerOrBuildIn === 'string' ? BuiltInDataFormats[providerOrBuildIn as keyof BuiltInDataFormats] as unknown as P : providerOrBuildIn as P;
-        const cell = StateObjectCell.resolve(this.dataState, data);
-        if (!cell) {
-            throw new Error('Could not resolve data cell.');
-        }
-        return provider.apply({ state: this.dataState, plugin: this.plugin }, cell, params);
-    }
-
-    // async test() {
-    //     const { data } = await this.download({ url: '' });
-    //     const cif = await this.parse('mmcif', data);
-    // }
-
-    constructor(public plugin: PluginContext) {
-        objectForEach(BuiltInDataFormats, f => this.formats.push(f));
-    }
-}

+ 0 - 17
src/mol-plugin-state/manager/data/actions.ts

@@ -1,17 +0,0 @@
-/**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { BuilderAction } from '../base';
-import { StateTransformer } from '../../../mol-state';
-import { Download as DownloadData, ReadFile } from '../../transforms/data';
-
-export const Download = BuilderAction((builder, params: StateTransformer.Params<DownloadData>, { options }) => {
-    return builder.apply(DownloadData, params, options);
-});
-
-export const OpenFile = BuilderAction((builder, params: StateTransformer.Params<ReadFile>, { options }) => {
-    return builder.apply(ReadFile, params, options);
-});

+ 0 - 50
src/mol-plugin-state/manager/data/formats.ts

@@ -1,50 +0,0 @@
-/**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { DataFormatProvider } from './provider';
-import { PluginContext } from '../../../mol-plugin/context';
-import { FileInfo } from '../../../mol-util/file-info';
-import { StateTransforms } from '../../transforms';
-
-export const MmcifFormatProvider = DataFormatProvider({
-    id: 'mmcif',
-    display: { name: 'mmCIF', group: 'Molecule' },
-    extensions: { text: ['cif', 'mmcif', 'mcif'], binary: ['bcif'] }
-})({
-    isApplicable(plugin: PluginContext, data: string | Uint8Array, info?: FileInfo): boolean {
-        // TODO: check CIF variants
-        return true;
-    },
-    async apply({ plugin, state }, data) {
-        const dictionary = state.build().to(data).apply(StateTransforms.Data.ParseCif, void 0, { state: { isGhost: true } });
-        const trajectory = dictionary.apply(StateTransforms.Model.TrajectoryFromMmCif);
-        await plugin.runTask(state.updateTree(trajectory));
-        return { dictionary: dictionary.selector, trajectory: trajectory.selector };
-    }
-});
-
-export const PdbFormatProvider = DataFormatProvider({
-    id: 'pdb',
-    display: { name: 'PDB', group: 'Molecule' },
-    extensions: { text: ['pdb', 'ent'] }
-})({
-    async apply({ plugin, state }, data) {
-        const trajectory = state.build().to(data).apply(StateTransforms.Model.TrajectoryFromPDB);
-        await plugin.runTask(state.updateTree(trajectory));
-        return { trajectory: trajectory.selector };
-    }
-});
-
-export const BuiltInDataFormats = {
-    'mmcif': MmcifFormatProvider,
-    'pdb': PdbFormatProvider
-}
-export type BuiltInDataFormats = typeof BuiltInDataFormats
-
-// export const TrajectoryFormatProviders = {
-//     'mmcif': MmcifFormatProvider,
-//     'pdb': PdbFormatProvider
-// }

+ 0 - 50
src/mol-plugin-state/manager/data/provider.ts

@@ -1,50 +0,0 @@
-/**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { PluginContext } from '../../../mol-plugin/context';
-import { State, StateObjectCell } from '../../../mol-state';
-import { ParamDefinition as PD } from '../../../mol-util/param-definition';
-import { PluginStateObject } from '../../objects';
-import { FileInfo } from '../../../mol-util/file-info';
-
-export { DataFormatProvider }
-
-type DataFormatProvider<Id extends string = string, D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String = PluginStateObject.Data.Binary | PluginStateObject.Data.String, P = any, S = {}> =
-    DataFormatProvider.Base<Id, P> & DataFormatProvider.Definition<D, P, S>
-
-function DataFormatProvider<Id extends string, P = {}>(provider: DataFormatProvider.Base<Id, P>) {
-    return function<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String, S>(p: DataFormatProvider.Definition<D, P, S>): DataFormatProvider<Id, D, P, S> {
-        return { ...provider, ...p };
-    };
-}
-
-namespace DataFormatProvider {
-    export type Ret<P extends DataFormatProvider> = P extends DataFormatProvider<any, any, any, infer T> ? T : never
-    export type Data<P extends DataFormatProvider> = P extends DataFormatProvider<any, infer T, any, any> ? T : never
-    export type Params<P extends DataFormatProvider> = P extends DataFormatProvider<any, any, infer T, any> ? T : never
-
-    export interface Base<Id extends string = string, P = any> {
-        id: Id,
-        display: { name: string, group?: string, description?: string },
-        extensions: { text?: string[], binary?: string[] },
-        params?(plugin: PluginContext, data: string | Uint8Array, info?: FileInfo): PD.Def<P>
-        // TODO: default representation
-        // defaultRepresentation?: RepresenatationProvider
-    }
-    export interface Definition<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String = PluginStateObject.Data.Binary | PluginStateObject.Data.String, P = any, S = any> {
-        isApplicable?(plugin: PluginContext, data: string | Uint8Array, info?: FileInfo): boolean,
-        apply(ctx: { state: State, plugin: PluginContext }, data: StateObjectCell<D>, params: P): Promise<S>
-    }
-}
-
-// interface DataSourceProvider<Id extends string = string, Format extends DataFormatProvider = DataFormatProvider, P = any, S = {}> {
-//     id: Id,
-//     display: { name: string, group?: string, description?: string },
-//     format: Format,
-//     apply(ctx: { ctx: RuntimeContext, state: State, plugin: PluginContext }, params: P): Promise<S>,
-//     params(plugin: PluginContext): PD.Def<P>,
-// }
-// function DataSourceProvider<Id extends string, Format extends DataFormatProvider, P, S>(provider: DataSourceProvider<Id, Format, P, S>) { return provider; }

+ 0 - 27
src/mol-plugin-state/manager/data/sources.ts

@@ -1,27 +0,0 @@
-/**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-// import { DataSourceProvider } from './provider';
-// import { MmcifFormatProvider } from './formats';
-// import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
-// import { Download } from './actions';
-
-// TODO: basic types string, binary (both download and from file)
-// TODO: decompress functionality (string|binary --> string|binary)
-
-// export const PDBeUpdatedMmcifDataSource = DataSourceProvider({
-//     id: 'pdbe-updated-mmcif',
-//     display: { name: 'PDBe Updated mmCIF', group: 'Molecule' },
-//     format: MmcifFormatProvider,
-//     params() {
-//         return { id: PD.Text('1cbs', { label: 'PDB Id(s)', description: 'One or more comma separated PDB ids.' }) }
-//     },
-//     async apply(ctx, params) {
-//         const download = Download(ctx.state.build().toRoot(), { url: `https://www.ebi.ac.uk/pdbe/static/entry/${params.id.toLowerCase()}_updated.cif` }, ctx);
-//         await ctx.state.updateTree(download).runInContext(ctx.ctx);
-//         return 0;
-//     }
-// })

+ 53 - 22
src/mol-plugin-state/manager/structure/hierarchy-state.ts

@@ -8,10 +8,12 @@ import { PluginStateObject as SO } from '../../objects';
 import { StateObject, StateTransform, State, StateObjectCell, StateTree } from '../../../mol-state';
 import { StructureBuilderTags } from '../../builder/structure';
 import { RepresentationProviderTags } from '../../builder/structure/provider';
+import { StructureRepresentationInteractionTags } from '../../../mol-plugin/behavior/dynamic/selection/structure-representation-interaction';
 
 export function buildStructureHierarchy(state: State, previous?: StructureHierarchy) {
     const build = BuildState(state, previous || StructureHierarchy());
-    StateTree.doPreOrder(state.tree, state.tree.root, build, visitCell);
+//    StateTree.doPreOrder(state.tree, state.tree.root, build, visitCell);
+    doPreOrder(state.tree, build);
     if (previous) previous.refs.forEach(isRemoved, build);
     return { hierarchy: build.hierarchy, added: build.added, updated: build.updated, removed: build.removed };
 }
@@ -147,34 +149,46 @@ function createOrUpdateRef<R extends HierarchyRef, C extends any[]>(state: Build
     return ref;
 }
 
-const tagMap: [string, (state: BuildState, cell: StateObjectCell) => boolean | void][] = [
+const tagMap: [string, (state: BuildState, cell: StateObjectCell) => boolean | void, (state: BuildState) => any][] = [
     [StructureBuilderTags.Trajectory, (state, cell) => {
         state.currentTrajectory = createOrUpdateRefList(state, cell, state.hierarchy.trajectories, TrajectoryRef, cell);
-    }],
+    }, state => state.currentTrajectory = void 0],
     [StructureBuilderTags.Model, (state, cell) => {
         if (!state.currentTrajectory) return false;
         state.currentModel = createOrUpdateRefList(state, cell, state.currentTrajectory.models, ModelRef, cell, state.currentTrajectory);
-    }],
+    }, state => state.currentModel = void 0],
     [StructureBuilderTags.ModelProperties, (state, cell) => {
         if (!state.currentModel) return false;
         state.currentModel.properties = createOrUpdateRef(state, cell, state.currentModel.properties, ModelPropertiesRef, cell, state.currentModel);
-    }],
+    }, state => { }],
     [StructureBuilderTags.Structure, (state, cell) => {
         if (!state.currentModel) return false;
         state.currentStructure = createOrUpdateRefList(state, cell, state.currentModel.structures, StructureRef, cell, state.currentModel);
-    }],
+    }, state => state.currentStructure = void 0],
     [StructureBuilderTags.StructureProperties, (state, cell) => {
         if (!state.currentStructure) return false;
         state.currentStructure.properties = createOrUpdateRef(state, cell, state.currentStructure.properties, StructurePropertiesRef, cell, state.currentStructure);
-    }],
+    }, state => { }],
     [StructureBuilderTags.Component, (state, cell) => {
         if (!state.currentStructure) return false;
         state.currentComponent = createOrUpdateRefList(state, cell, state.currentStructure.components, StructureComponentRef, cell, state.currentStructure);
-    }],
+    }, state => state.currentComponent = void 0],
     [RepresentationProviderTags.Representation, (state, cell) => {
         if (!state.currentComponent) return false;
         createOrUpdateRefList(state, cell, state.currentComponent.representations, StructureRepresentationRef, cell, state.currentComponent);
-    }]
+    }, state => { }],
+    [StructureRepresentationInteractionTags.ResidueSel, (state, cell) => {
+        if (!state.currentStructure) return false;
+        if (!state.currentStructure.currentFocus) state.currentStructure.currentFocus = { };
+        state.currentStructure.currentFocus.focus = StructureComponentRef(cell, state.currentStructure);
+        state.currentComponent = state.currentStructure.currentFocus.focus;
+    }, state => state.currentComponent = void 0],
+    [StructureRepresentationInteractionTags.SurrSel, (state, cell) => {
+        if (!state.currentStructure) return false;
+        if (!state.currentStructure.currentFocus) state.currentStructure.currentFocus = { };
+        state.currentStructure.currentFocus.surroundings = StructureComponentRef(cell, state.currentStructure);
+        state.currentComponent = state.currentStructure.currentFocus.surroundings;
+    }, state => state.currentComponent = void 0]
 ]
 
 function isValidCell(cell?: StateObjectCell): cell is StateObjectCell {
@@ -184,27 +198,44 @@ function isValidCell(cell?: StateObjectCell): cell is StateObjectCell {
     return true;
 }
 
-function visitCell(t: StateTransform, tree: StateTree, state: BuildState): boolean {
-    const cell = state.state.cells.get(t.ref);
-    if (!isValidCell(cell)) return false;
+function isRemoved(this: BuildState, ref: HierarchyRef) {
+    const { cell } = ref;
+    if (isValidCell(cell)) return;
+    this.removed.push(ref);
+}
+
+type VisitorCtx = { tree: StateTree, state: BuildState };
+
+function _preOrderFunc(this: VisitorCtx, c: StateTransform.Ref | undefined) { _doPreOrder(this, this.tree.transforms.get(c!)!); }
+function _doPreOrder(ctx: VisitorCtx, root: StateTransform) {
+    const { state } = ctx;
+    const cell = state.state.cells.get(root.ref);
+    if (!isValidCell(cell)) return;
 
-    for (const [t, f] of tagMap) {
+    let onLeave: undefined | ((state: BuildState) => any) = void 0;
+    for (const [t, f, l] of tagMap) {
         if (StateObject.hasTag(cell.obj!, t)) {
             const stop = f(state, cell);
-            if (stop === false) return false;
-            return true;
+            if (stop === false) return;
+            onLeave = l;
+            break;
         }
     }
 
-    if (state.currentComponent && SO.Molecule.Structure.Representation3D.is(cell.obj)) {
+    if (!onLeave && state.currentComponent && SO.Molecule.Structure.Representation3D.is(cell.obj)) {
         createOrUpdateRefList(state, cell, state.currentComponent.representations, StructureRepresentationRef, cell, state.currentComponent);
     }
 
-    return true;
-}
+    const children = ctx.tree.children.get(root.ref);
+    if (children && children.size) {
+        children.forEach(_preOrderFunc, ctx);
+    }
 
-function isRemoved(this: BuildState, ref: HierarchyRef) {
-    const { cell } = ref;
-    if (isValidCell(cell)) return;
-    this.removed.push(ref);
+    if (onLeave) onLeave(state);
 }
+
+function doPreOrder(tree: StateTree, state: BuildState): BuildState {
+    const ctx: VisitorCtx = { tree, state };
+    _doPreOrder(ctx, tree.root);
+    return ctx.state;
+}

+ 23 - 23
src/mol-plugin/behavior/dynamic/selection/structure-representation-interaction.ts

@@ -35,7 +35,7 @@ const StructureRepresentationInteractionParams = {
 }
 type StructureRepresentationInteractionProps = PD.Values<typeof StructureRepresentationInteractionParams>
 
-enum Tags {
+export enum StructureRepresentationInteractionTags {
     Group = 'structure-interaction-group',
     ResidueSel = 'structure-interaction-residue-sel',
     ResidueRepr = 'structure-interaction-residue-repr',
@@ -44,7 +44,7 @@ enum Tags {
     SurrNciRepr = 'structure-interaction-surr-nci-repr'
 }
 
-const TagSet: Set<Tags> = new Set([Tags.Group, Tags.ResidueSel, Tags.ResidueRepr, Tags.SurrSel, Tags.SurrRepr, Tags.SurrNciRepr])
+const TagSet: Set<StructureRepresentationInteractionTags> = new Set([StructureRepresentationInteractionTags.Group, StructureRepresentationInteractionTags.ResidueSel, StructureRepresentationInteractionTags.ResidueRepr, StructureRepresentationInteractionTags.SurrSel, StructureRepresentationInteractionTags.SurrRepr, StructureRepresentationInteractionTags.SurrNciRepr])
 
 export class StructureRepresentationInteractionBehavior extends PluginBehavior.WithSubscribers<StructureRepresentationInteractionProps> {
 
@@ -78,35 +78,35 @@ export class StructureRepresentationInteractionBehavior extends PluginBehavior.W
 
         if (!refs['structure-interaction-group']) {
             refs['structure-interaction-group'] = builder.to(cell).group(StateTransforms.Misc.CreateGroup,
-                { label: 'Current Focus' }, { tags: Tags.Group }).ref;
+                { label: 'Current Focus' }, { tags: StructureRepresentationInteractionTags.Group }).ref;
         }
 
         // Selections
-        if (!refs[Tags.ResidueSel]) {
-            refs[Tags.ResidueSel] = builder.to(refs['structure-interaction-group']).apply(StateTransforms.Model.StructureSelectionFromBundle,
-                { bundle: { } as any, label: 'Residue' }, { tags: Tags.ResidueSel }).ref;
+        if (!refs[StructureRepresentationInteractionTags.ResidueSel]) {
+            refs[StructureRepresentationInteractionTags.ResidueSel] = builder.to(refs['structure-interaction-group']).apply(StateTransforms.Model.StructureSelectionFromBundle,
+                { bundle: { } as any, label: 'Residue' }, { tags: StructureRepresentationInteractionTags.ResidueSel }).ref;
         }
 
-        if (!refs[Tags.SurrSel]) {
-            refs[Tags.SurrSel] = builder.to(refs['structure-interaction-group']).apply(StateTransforms.Model.StructureSelectionFromExpression,
-                { expression: { } as any, label: 'Surroundings' }, { tags: Tags.SurrSel }).ref;
+        if (!refs[StructureRepresentationInteractionTags.SurrSel]) {
+            refs[StructureRepresentationInteractionTags.SurrSel] = builder.to(refs['structure-interaction-group']).apply(StateTransforms.Model.StructureSelectionFromExpression,
+                { expression: { } as any, label: 'Surroundings' }, { tags: StructureRepresentationInteractionTags.SurrSel }).ref;
         }
 
         // Representations
         // TODO: ability to customize how it looks in the behavior params
-        if (!refs[Tags.ResidueRepr]) {
-            refs[Tags.ResidueRepr] = builder.to(refs['structure-interaction-residue-sel']!).apply(StateTransforms.Representation.StructureRepresentation3D,
-                this.createResVisualParams(cell.obj!.data), { tags: Tags.ResidueRepr }).ref;
+        if (!refs[StructureRepresentationInteractionTags.ResidueRepr]) {
+            refs[StructureRepresentationInteractionTags.ResidueRepr] = builder.to(refs['structure-interaction-residue-sel']!).apply(StateTransforms.Representation.StructureRepresentation3D,
+                this.createResVisualParams(cell.obj!.data), { tags: StructureRepresentationInteractionTags.ResidueRepr }).ref;
         }
 
-        if (!refs[Tags.SurrRepr]) {
-            refs[Tags.SurrRepr] = builder.to(refs['structure-interaction-surr-sel']!).apply(StateTransforms.Representation.StructureRepresentation3D,
-                this.createSurVisualParams(cell.obj!.data), { tags: Tags.SurrRepr }).ref;
+        if (!refs[StructureRepresentationInteractionTags.SurrRepr]) {
+            refs[StructureRepresentationInteractionTags.SurrRepr] = builder.to(refs['structure-interaction-surr-sel']!).apply(StateTransforms.Representation.StructureRepresentation3D,
+                this.createSurVisualParams(cell.obj!.data), { tags: StructureRepresentationInteractionTags.SurrRepr }).ref;
         }
 
-        if (!refs[Tags.SurrNciRepr]) {
-            refs[Tags.SurrNciRepr] = builder.to(refs['structure-interaction-surr-sel']!).apply(StateTransforms.Representation.StructureRepresentation3D,
-                this.createSurNciVisualParams(cell.obj!.data), { tags: Tags.SurrNciRepr }).ref;
+        if (!refs[StructureRepresentationInteractionTags.SurrNciRepr]) {
+            refs[StructureRepresentationInteractionTags.SurrNciRepr] = builder.to(refs['structure-interaction-surr-sel']!).apply(StateTransforms.Representation.StructureRepresentation3D,
+                this.createSurNciVisualParams(cell.obj!.data), { tags: StructureRepresentationInteractionTags.SurrNciRepr }).ref;
         }
 
         return { state, builder, refs };
@@ -114,7 +114,7 @@ export class StructureRepresentationInteractionBehavior extends PluginBehavior.W
 
     private clear(root: StateTransform.Ref) {
         const state = this.plugin.state.dataState;
-        const groups = state.select(StateSelection.Generators.byRef(root).subtree().withTag(Tags.Group));
+        const groups = state.select(StateSelection.Generators.byRef(root).subtree().withTag(StructureRepresentationInteractionTags.Group));
         if (groups.length === 0) return;
 
         const update = state.build();
@@ -123,8 +123,8 @@ export class StructureRepresentationInteractionBehavior extends PluginBehavior.W
         for (const g of groups) {
             // TODO: update props of the group node to ghost
 
-            const res = StateSelection.findTagInSubtree(state.tree, g.transform.ref, Tags.ResidueSel);
-            const surr = StateSelection.findTagInSubtree(state.tree, g.transform.ref, Tags.SurrSel);
+            const res = StateSelection.findTagInSubtree(state.tree, g.transform.ref, StructureRepresentationInteractionTags.ResidueSel);
+            const surr = StateSelection.findTagInSubtree(state.tree, g.transform.ref, StructureRepresentationInteractionTags.SurrSel);
             if (res) update.to(res).update(StateTransforms.Model.StructureSelectionFromBundle, old => ({ ...old, bundle }));
             if (surr) update.to(surr).update(StateTransforms.Model.StructureSelectionFromExpression, old => ({ ...old, expression }));
         }
@@ -194,8 +194,8 @@ export class StructureRepresentationInteractionBehavior extends PluginBehavior.W
 
                 const { state, builder, refs } = this.ensureShape(parent);
 
-                builder.to(refs[Tags.ResidueSel]!).update(StateTransforms.Model.StructureSelectionFromBundle, old => ({ ...old, bundle: residueBundle }));
-                builder.to(refs[Tags.SurrSel]!).update(StateTransforms.Model.StructureSelectionFromExpression, old => ({ ...old, expression: surroundings }));
+                builder.to(refs[StructureRepresentationInteractionTags.ResidueSel]!).update(StateTransforms.Model.StructureSelectionFromBundle, old => ({ ...old, bundle: residueBundle }));
+                builder.to(refs[StructureRepresentationInteractionTags.SurrSel]!).update(StateTransforms.Model.StructureSelectionFromExpression, old => ({ ...old, expression: surroundings }));
 
                 PluginCommands.State.Update(this.plugin, { state, tree: builder, options: { doNotLogTiming: true, doNotUpdateCurrent: true } });
             }