|
@@ -4,26 +4,26 @@
|
|
|
* @author David Sehnal <david.sehnal@gmail.com>
|
|
|
*/
|
|
|
|
|
|
+import { VisualQualityOptions } from '../../../mol-geo/geometry/base';
|
|
|
+import { InteractionsProvider } from '../../../mol-model-props/computed/interactions';
|
|
|
import { Structure, StructureElement } from '../../../mol-model/structure';
|
|
|
import { structureAreIntersecting, structureSubtract, structureUnion } from '../../../mol-model/structure/query/utils/structure-set';
|
|
|
+import { setSubtreeVisibility } from '../../../mol-plugin/behavior/static/state';
|
|
|
import { PluginContext } from '../../../mol-plugin/context';
|
|
|
import { StateBuilder, StateTransformer } from '../../../mol-state';
|
|
|
import { Task } from '../../../mol-task';
|
|
|
import { UUID } from '../../../mol-util';
|
|
|
-import { Color } from '../../../mol-util/color';
|
|
|
+import { arraySetAdd } from '../../../mol-util/array';
|
|
|
import { ColorNames } from '../../../mol-util/color/names';
|
|
|
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
|
|
import { StructureRepresentationProvider } from '../../builder/structure/provider';
|
|
|
-import { StructureComponentParams } from '../../helpers/structure-component';
|
|
|
-import { setStructureOverpaint } from '../../helpers/structure-overpaint';
|
|
|
-import { StructureSelectionQuery, StructureSelectionQueryOptions } from '../../helpers/structure-selection-query';
|
|
|
-import { HierarchyRef, StructureComponentRef, StructureRef, StructureRepresentationRef } from './hierarchy-state';
|
|
|
import { PluginComponent } from '../../component';
|
|
|
-import { VisualQualityOptions } from '../../../mol-geo/geometry/base';
|
|
|
-import { InteractionsProvider } from '../../../mol-model-props/computed/interactions';
|
|
|
-import { StructureRepresentation3D } from '../../transforms/representation';
|
|
|
-import { arraySetAdd } from '../../../mol-util/array';
|
|
|
+import { StructureComponentParams } from '../../helpers/structure-component';
|
|
|
+import { setStructureOverpaint, clearStructureOverpaint } from '../../helpers/structure-overpaint';
|
|
|
+import { StructureSelectionQuery, StructureSelectionQueryOptions, StructureSelectionQueries } from '../../helpers/structure-selection-query';
|
|
|
import { CustomStructureProperties } from '../../transforms/model';
|
|
|
+import { StructureRepresentation3D } from '../../transforms/representation';
|
|
|
+import { HierarchyRef, StructureComponentRef, StructureRef, StructureRepresentationRef } from './hierarchy-state';
|
|
|
|
|
|
export { StructureComponentManager };
|
|
|
|
|
@@ -42,6 +42,10 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
|
|
|
return this.plugin.managers.structure.hierarchy.state.currentStructures;
|
|
|
}
|
|
|
|
|
|
+ get pivotStructure(): StructureRef | undefined {
|
|
|
+ return this.currentStructures[0];
|
|
|
+ }
|
|
|
+
|
|
|
async setOptions(options: StructureComponentManager.Options) {
|
|
|
const interactionChanged = options.interactions !== this.state.options.interactions;
|
|
|
this.updateState({ options });
|
|
@@ -118,53 +122,113 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
|
|
|
return this.clearComponents(structures);
|
|
|
}
|
|
|
|
|
|
- removeRepresentations(components: StructureComponentRef[], pivot: StructureRepresentationRef) {
|
|
|
+ selectThis(components: ReadonlyArray<StructureComponentRef>) {
|
|
|
+ const mng = this.plugin.managers.structure.selection;
|
|
|
+ mng.clear();
|
|
|
+ for (const c of components) {
|
|
|
+ const loci = Structure.toSubStructureElementLoci(c.structure.cell.obj!.data, c.cell.obj?.data!)
|
|
|
+ mng.fromLoci('set', loci);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ modifyByCurrentSelection(components: ReadonlyArray<StructureComponentRef>, action: 'union' | 'subtract') {
|
|
|
+ return this.plugin.runTask(Task.create('Subtract', async taskCtx => {
|
|
|
+ const b = this.dataState.build();
|
|
|
+ for (const c of components) {
|
|
|
+ const selection = this.plugin.managers.structure.selection.getStructure(c.structure.cell.obj!.data);
|
|
|
+ if (!selection || selection.elementCount === 0) continue;
|
|
|
+ this.updateComponent(b, c, selection, action);
|
|
|
+ }
|
|
|
+ await this.dataState.updateTree(b).runInContext(taskCtx);
|
|
|
+ }));
|
|
|
+ }
|
|
|
+
|
|
|
+ toggleVisibility(components: ReadonlyArray<StructureComponentRef>) {
|
|
|
+ if (components.length === 0) return;
|
|
|
+ const isHidden = !components[0].cell.state.isHidden;
|
|
|
+ for (const c of components) {
|
|
|
+ setSubtreeVisibility(this.dataState, c.cell.transform.ref, isHidden);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ removeRepresentations(components: ReadonlyArray<StructureComponentRef>, pivot?: StructureRepresentationRef) {
|
|
|
if (components.length === 0) return;
|
|
|
- const index = components[0].representations.indexOf(pivot);
|
|
|
- if (index < 0) return;
|
|
|
|
|
|
const toRemove: HierarchyRef[] = [];
|
|
|
- for (const c of components) {
|
|
|
- if (index >= c.representations.length) continue;
|
|
|
- toRemove.push(c.representations[index]);
|
|
|
+ if (pivot) {
|
|
|
+ const index = components[0].representations.indexOf(pivot);
|
|
|
+ if (index < 0) return;
|
|
|
+
|
|
|
+ for (const c of components) {
|
|
|
+ if (c.representations[index]) toRemove.push(c.representations[index]);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ for (const c of components) {
|
|
|
+ for (const r of c.representations) {
|
|
|
+ toRemove.push(r);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
return this.plugin.managers.structure.hierarchy.remove(toRemove);
|
|
|
}
|
|
|
|
|
|
- modify(action: StructureComponentManager.ModifyAction, structures?: ReadonlyArray<StructureRef>) {
|
|
|
+ async addRepresentation(components: ReadonlyArray<StructureComponentRef>, type: string) {
|
|
|
+ if (components.length === 0) return;
|
|
|
+
|
|
|
+ const { showHydrogens, visualQuality: quality } = this.state.options;
|
|
|
+ const ignoreHydrogens = !showHydrogens;
|
|
|
+ const params = () => ({ ignoreHydrogens, quality });
|
|
|
+
|
|
|
+ for (const component of components) {
|
|
|
+ await this.plugin.builders.structure.representation.addRepresentation(component.cell, {
|
|
|
+ repr: [this.plugin.structureRepresentation.registry.get(type), params]
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async add(params: StructureComponentManager.AddParams, structures?: ReadonlyArray<StructureRef>) {
|
|
|
return this.plugin.dataTransaction(async () => {
|
|
|
- if (!structures) structures = this.currentStructures;
|
|
|
- if (structures.length === 0) return;
|
|
|
-
|
|
|
- switch (action.kind) {
|
|
|
- case 'add': await this.modifyAdd(action, structures); break;
|
|
|
- case 'merge': await this.modifyMerge(action, structures); break;
|
|
|
- case 'subtract': await this.modifySubtract(action, structures); break;
|
|
|
- case 'color': await this.modifyColor(action, structures); break;
|
|
|
+ const xs = structures || this.currentStructures;
|
|
|
+ if (xs.length === 0) return;
|
|
|
+
|
|
|
+ const componentKey = UUID.create22();
|
|
|
+ for (const s of xs) {
|
|
|
+ const component = await this.plugin.builders.structure.tryCreateQueryComponent({
|
|
|
+ structure: s.cell,
|
|
|
+ query: params.selection,
|
|
|
+ key: componentKey,
|
|
|
+ label: params.label || (params.selection === StructureSelectionQueries.current ? 'Custom Selection' : ''),
|
|
|
+ });
|
|
|
+ if (params.representation === 'none' || !component) continue;
|
|
|
+ await this.plugin.builders.structure.representation.addRepresentation(component, {
|
|
|
+ repr: this.plugin.structureRepresentation.registry.get(params.representation)
|
|
|
+ });
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- private async modifyAdd(params: StructureComponentManager.ModifyActionAdd, structures: ReadonlyArray<StructureRef>) {
|
|
|
- const componentKey = UUID.create22();
|
|
|
- for (const s of structures) {
|
|
|
- const component = await this.plugin.builders.structure.tryCreateQueryComponent({
|
|
|
- structure: s.cell,
|
|
|
- query: params.selection,
|
|
|
- key: componentKey,
|
|
|
- label: params.label,
|
|
|
- });
|
|
|
- if (params.representation === 'none' || !component) continue;
|
|
|
- await this.plugin.builders.structure.representation.addRepresentation(component, {
|
|
|
- repr: this.plugin.structureRepresentation.registry.get(params.representation)
|
|
|
- });
|
|
|
- }
|
|
|
+ async applyColor(params: StructureComponentManager.ColorParams, structures?: ReadonlyArray<StructureRef>) {
|
|
|
+ return this.plugin.dataTransaction(async () => {
|
|
|
+ const xs = structures || this.currentStructures;
|
|
|
+ if (xs.length === 0) return;
|
|
|
+ const getLoci = (s: Structure) => this.plugin.managers.structure.selection.getLoci(s);
|
|
|
+
|
|
|
+ for (const s of xs) {
|
|
|
+ if (params.action.name === 'reset') {
|
|
|
+ await clearStructureOverpaint(this.plugin, s.components, params.representations);
|
|
|
+ } else {
|
|
|
+ const p = params.action.params;
|
|
|
+ await setStructureOverpaint(this.plugin, s.components, p.color, getLoci, params.representations, p.opacity);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
- private updateComponent(builder: StateBuilder.Root, component: StructureComponentRef, by: Structure, action: 'union' | 'subtract', checkIntersecting: boolean) {
|
|
|
+ private updateComponent(builder: StateBuilder.Root, component: StructureComponentRef, by: Structure, action: 'union' | 'subtract') {
|
|
|
const structure = component.cell.obj?.data;
|
|
|
if (!structure) return;
|
|
|
- if (checkIntersecting && !structureAreIntersecting(structure, by)) return;
|
|
|
+ if (action === 'subtract' && !structureAreIntersecting(structure, by)) return;
|
|
|
|
|
|
const parent = component.structure.cell.obj?.data!;
|
|
|
const modified = action === 'union' ? structureUnion(parent, [structure, by]) : structureSubtract(structure, by);
|
|
@@ -182,41 +246,6 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private async modifyMerge(params: StructureComponentManager.ModifyActionMerge, structures: ReadonlyArray<StructureRef>) {
|
|
|
- return this.plugin.runTask(Task.create('Merge', async taskCtx => {
|
|
|
- const b = this.dataState.build();
|
|
|
- for (const s of structures) {
|
|
|
- const by = await StructureSelectionQuery.getStructure(this.plugin, taskCtx, params.selection, s.cell.obj?.data!);
|
|
|
- for (const c of s.components) {
|
|
|
- if (params.componentKey !== 'intersecting' && params.componentKey !== c.key) continue;
|
|
|
- this.updateComponent(b, c, by, 'union', params.componentKey === 'intersecting' );
|
|
|
- }
|
|
|
- }
|
|
|
- await this.dataState.updateTree(b).runInContext(taskCtx);
|
|
|
- }));
|
|
|
- }
|
|
|
-
|
|
|
- private async modifySubtract(params: StructureComponentManager.ModifyActionSubtract, structures: ReadonlyArray<StructureRef>) {
|
|
|
- return this.plugin.runTask(Task.create('Subtract', async taskCtx => {
|
|
|
- const b = this.dataState.build();
|
|
|
- for (const s of structures) {
|
|
|
- const by = await StructureSelectionQuery.getStructure(this.plugin, taskCtx, params.selection, s.cell.obj?.data!);
|
|
|
- for (const c of s.components) {
|
|
|
- if (params.componentKey !== 'intersecting' && params.componentKey !== c.key) continue;
|
|
|
- this.updateComponent(b, c, by, 'subtract', true);
|
|
|
- }
|
|
|
- }
|
|
|
- await this.dataState.updateTree(b).runInContext(taskCtx);
|
|
|
- }));
|
|
|
- }
|
|
|
-
|
|
|
- private async modifyColor(params: StructureComponentManager.ModifyActionColor, structures: ReadonlyArray<StructureRef>) {
|
|
|
- const getLoci = (s: Structure) => this.plugin.managers.structure.selection.getLoci(s);
|
|
|
- for (const s of structures) {
|
|
|
- await setStructureOverpaint(this.plugin, s.components, params.action.name === 'color' ? params.action.params : -1, getLoci);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
private get dataState() {
|
|
|
return this.plugin.state.dataState;
|
|
|
}
|
|
@@ -247,63 +276,43 @@ namespace StructureComponentManager {
|
|
|
interactions: PD.Group(InteractionsProvider.defaultParams, { label: 'Non-covalent Interactions' }),
|
|
|
}
|
|
|
export type Options = PD.Values<typeof OptionsParams>
|
|
|
-
|
|
|
- export type ActionType = 'add' | 'merge' | 'subtract' | 'color'
|
|
|
-
|
|
|
+
|
|
|
const SelectionParam = PD.Select(StructureSelectionQueryOptions[1][0], StructureSelectionQueryOptions)
|
|
|
+
|
|
|
+ export function getAddParams(plugin: PluginContext) {
|
|
|
+ return {
|
|
|
+ selection: SelectionParam,
|
|
|
+ representation: getRepresentationTypesSelect(plugin, plugin.managers.structure.hierarchy.state.currentStructures[0], [['none', '< None >']]),
|
|
|
+ label: PD.Text('')
|
|
|
+ };
|
|
|
+ }
|
|
|
+ export type AddParams = { selection: StructureSelectionQuery, label: string, representation: string }
|
|
|
+
|
|
|
+ export function getColorParams(plugin: PluginContext, pivot: StructureRef | StructureComponentRef | undefined) {
|
|
|
+ return {
|
|
|
+ action: PD.MappedStatic('color', {
|
|
|
+ color: PD.Group({
|
|
|
+ color: PD.Color(ColorNames.blue, { isExpanded: true }),
|
|
|
+ opacity: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
|
|
|
+ }, { isFlat: true }),
|
|
|
+ reset: PD.EmptyGroup()
|
|
|
+ }),
|
|
|
+ representations: PD.MultiSelect([], getRepresentationTypes(plugin, pivot), { emptyValue: 'All' })
|
|
|
+ };
|
|
|
+ }
|
|
|
+ export type ColorParams = PD.Values<ReturnType<typeof getColorParams>>
|
|
|
|
|
|
- function getComponentsOptions(plugin: PluginContext, custom: [string, string][], label?: string) {
|
|
|
- const types = [
|
|
|
- ...custom,
|
|
|
- ...plugin.managers.structure.hierarchy.componentGroups.map(g => [g[0].key!, g[0].cell.obj?.label])
|
|
|
- ] as [string, string][];
|
|
|
- return PD.Select(types[0][0], types, { label });
|
|
|
+ export function getRepresentationTypes(plugin: PluginContext, pivot: StructureRef | StructureComponentRef | undefined) {
|
|
|
+ return pivot?.cell.obj?.data
|
|
|
+ ? plugin.structureRepresentation.registry.getApplicableTypes(pivot.cell.obj?.data!)
|
|
|
+ : plugin.structureRepresentation.registry.types;
|
|
|
}
|
|
|
|
|
|
- function getRepresentationTypes(plugin: PluginContext, pivot: StructureRef | undefined, custom: [string, string][], label?: string) {
|
|
|
+ function getRepresentationTypesSelect(plugin: PluginContext, pivot: StructureRef | undefined, custom: [string, string][], label?: string) {
|
|
|
const types = [
|
|
|
...custom,
|
|
|
- ...(pivot?.cell.obj?.data
|
|
|
- ? plugin.structureRepresentation.registry.getApplicableTypes(pivot.cell.obj?.data!)
|
|
|
- : plugin.structureRepresentation.registry.types)
|
|
|
+ ...getRepresentationTypes(plugin, pivot)
|
|
|
] as [string, string][];
|
|
|
return PD.Select(types[0][0], types, { label });
|
|
|
}
|
|
|
-
|
|
|
- export function getActionParams(plugin: PluginContext, action: ActionType) {
|
|
|
- switch (action) {
|
|
|
- case 'add':
|
|
|
- return {
|
|
|
- kind: PD.Value<ActionType>(action, { isHidden: true }),
|
|
|
- selection: SelectionParam,
|
|
|
- label: PD.Text(''),
|
|
|
- representation: getRepresentationTypes(plugin, plugin.managers.structure.hierarchy.state.currentStructures[0], [['none', '< None >']])
|
|
|
- };
|
|
|
- case 'merge':
|
|
|
- case 'subtract':
|
|
|
- return {
|
|
|
- kind: PD.Value<ActionType>(action, { isHidden: true }),
|
|
|
- selection: SelectionParam,
|
|
|
- componentKey: getComponentsOptions(plugin, [['intersecting', '< Intersecting >']], 'Target')
|
|
|
- };
|
|
|
- case 'color':
|
|
|
- // TODO: ability to reset
|
|
|
- return {
|
|
|
- kind: PD.Value<ActionType>(action, { isHidden: true }),
|
|
|
- action: PD.MappedStatic('color', {
|
|
|
- color: PD.Color(ColorNames.blue, { label: 'Color', isExpanded: true }),
|
|
|
- reset: PD.EmptyGroup()
|
|
|
- }),
|
|
|
- // TODO: filter by representation type
|
|
|
- // representation: getRepresentationTypes(plugin, void 0, [['all', '< All >']])
|
|
|
- };
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- export type ModifyActionAdd = { kind: 'add', selection: StructureSelectionQuery, label: string, representation: string }
|
|
|
- export type ModifyActionMerge = { kind: 'merge', selection: StructureSelectionQuery, componentKey: 'intersecting' | string }
|
|
|
- export type ModifyActionSubtract = { kind: 'subtract', selection: StructureSelectionQuery, componentKey: 'intersecting' | string }
|
|
|
- export type ModifyActionColor = { kind: 'color', action: { name: 'color', params: Color } | { name: 'reset', params: any } } //, representationType?: string }
|
|
|
-
|
|
|
- export type ModifyAction = ModifyActionAdd | ModifyActionMerge | ModifyActionSubtract | ModifyActionColor
|
|
|
}
|