Browse Source

Merge branch 'master' of https://github.com/molstar/molstar

Alexander Rose 5 years ago
parent
commit
8b42a0fede
37 changed files with 406 additions and 192 deletions
  1. 3 2
      src/mol-model-props/common/custom-element-property.ts
  2. 3 1
      src/mol-model-props/common/custom-model-property.ts
  3. 2 1
      src/mol-model-props/common/custom-property.ts
  4. 3 1
      src/mol-model-props/common/custom-structure-property.ts
  5. 3 2
      src/mol-model-props/computed/representations/interactions.ts
  6. 3 2
      src/mol-model-props/computed/themes/accessible-surface-area.ts
  7. 3 2
      src/mol-model-props/computed/themes/interaction-type.ts
  8. 4 3
      src/mol-model-props/integrative/cross-link-restraint/color.ts
  9. 3 2
      src/mol-model-props/integrative/cross-link-restraint/representation.ts
  10. 3 2
      src/mol-model-props/pdbe/themes/structure-quality-report.ts
  11. 3 2
      src/mol-model-props/rcsb/representations/validation-report-clashes.ts
  12. 3 2
      src/mol-model-props/rcsb/themes/assembly-symmetry-cluster.ts
  13. 3 2
      src/mol-model-props/rcsb/themes/density-fit.ts
  14. 3 2
      src/mol-model-props/rcsb/themes/geometry-quality.ts
  15. 3 2
      src/mol-model-props/rcsb/themes/random-coil-index.ts
  16. 16 1
      src/mol-model/structure/common/custom-property.ts
  17. 20 32
      src/mol-plugin-state/actions/structure.ts
  18. 0 1
      src/mol-plugin-state/builder/parser.ts
  19. 17 14
      src/mol-plugin-state/builder/representation.ts
  20. 0 1
      src/mol-plugin-state/builder/structure-model.ts
  21. 134 0
      src/mol-plugin-state/builder/structure.ts
  22. 82 85
      src/mol-plugin-state/builder/structure/preset.ts
  23. 2 2
      src/mol-plugin-state/helpers/structure-component.ts
  24. 3 1
      src/mol-plugin-state/transforms.ts
  25. 24 2
      src/mol-plugin-state/transforms/model.ts
  26. 17 7
      src/mol-plugin-state/transforms/representation.ts
  27. 3 2
      src/mol-plugin/behavior/dynamic/custom-props/computed/accessible-surface-area.ts
  28. 2 2
      src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts
  29. 3 0
      src/mol-plugin/behavior/dynamic/custom-props/rcsb/validation-report.ts
  30. 5 3
      src/mol-plugin/context.ts
  31. 4 1
      src/mol-repr/representation.ts
  32. 3 2
      src/mol-repr/structure/representation/cartoon.ts
  33. 2 1
      src/mol-state/object.ts
  34. 2 1
      src/mol-state/state/builder.ts
  35. 4 3
      src/mol-theme/color/secondary-structure.ts
  36. 11 3
      src/mol-theme/theme.ts
  37. 7 0
      src/mol-util/param-definition.ts

+ 3 - 2
src/mol-model-props/common/custom-element-property.ts

@@ -104,8 +104,9 @@ namespace CustomElementProperty {
             getParams: () => ({}),
             defaultValues: {},
             isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && !!modelProperty.get(ctx.structure.models[0]).value,
-            ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
-                return data.structure ? modelProperty.attach(ctx, data.structure.models[0]) : Promise.resolve()
+            ensureCustomProperties: {
+                attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? modelProperty.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
+                detach: (_, data: ThemeDataContext) => data.structure && data.structure.models[0].customProperties.reference(modelProperty.descriptor, false)
             }
         }
     }

+ 3 - 1
src/mol-model-props/common/custom-model-property.ts

@@ -51,7 +51,8 @@ namespace CustomModelProperty {
             getParams: builder.getParams,
             defaultParams: builder.defaultParams,
             isApplicable: builder.isApplicable,
-            attach: async (ctx: CustomProperty.Context, data: Model, props: Partial<PD.Values<Params>> = {}) => {
+            attach: async (ctx: CustomProperty.Context, data: Model, props: Partial<PD.Values<Params>> = {}, addRef) => {
+                if (addRef) data.customProperties.reference(builder.descriptor, true);
                 const property = get(data)
                 const p = { ...property.props, ...props }
                 if (property.data.value && PD.areEqual(builder.defaultParams, property.props, p)) return
@@ -59,6 +60,7 @@ namespace CustomModelProperty {
                 data.customProperties.add(builder.descriptor);
                 set(data, p, value);
             },
+            ref: (data: Model, add: boolean) => data.customProperties.reference(builder.descriptor, add),
             get: (data: Model) => get(data)?.data,
             set: (data: Model, props: Partial<PD.Values<Params>> = {}) => {
                 const property = get(data)

+ 2 - 1
src/mol-model-props/common/custom-property.ts

@@ -31,7 +31,8 @@ namespace CustomProperty {
         readonly getParams: (data: Data) => Params
         readonly defaultParams: Params
         readonly isApplicable: (data: Data) => boolean
-        readonly attach: (ctx: Context, data: Data, props?: Partial<PD.Values<Params>>) => Promise<void>
+        readonly attach: (ctx: Context, data: Data, props?: Partial<PD.Values<Params>>, addRef?: boolean) => Promise<void>
+        readonly ref: (data: Data, add: boolean) => void
         readonly get: (data: Data) => ValueBox<Value | undefined>
         readonly set: (data: Data, props: PD.Values<Params>, value?: Value) => void
     }

+ 3 - 1
src/mol-model-props/common/custom-structure-property.ts

@@ -51,7 +51,8 @@ namespace CustomStructureProperty {
             getParams: builder.getParams,
             defaultParams: builder.defaultParams,
             isApplicable: builder.isApplicable,
-            attach: async (ctx: CustomProperty.Context, data: Structure, props: Partial<PD.Values<Params>> = {}) => {
+            attach: async (ctx: CustomProperty.Context, data: Structure, props: Partial<PD.Values<Params>> = {}, addRef) => {
+                if (addRef) data.customPropertyDescriptors.reference(builder.descriptor, true);
                 if (builder.type === 'root') data = data.root
                 const property = get(data)
                 const p = { ...property.props, ...props }
@@ -60,6 +61,7 @@ namespace CustomStructureProperty {
                 data.customPropertyDescriptors.add(builder.descriptor);
                 set(data, p, value);
             },
+            ref: (data: Structure, add: boolean) => data.customPropertyDescriptors.reference(builder.descriptor, add),
             get: (data: Structure) => get(data).data,
             set: (data: Structure, props: Partial<PD.Values<Params>> = {}, value?: Value) => {
                 if (builder.type === 'root') data = data.root

+ 3 - 2
src/mol-model-props/computed/representations/interactions.ts

@@ -46,7 +46,8 @@ export const InteractionsRepresentationProvider: StructureRepresentationProvider
     defaultColorTheme: { name: 'interaction-type' },
     defaultSizeTheme: { name: 'uniform' },
     isApplicable: (structure: Structure) => structure.elementCount > 0,
-    ensureCustomProperties: (ctx: CustomProperty.Context, structure: Structure) => {
-        return InteractionsProvider.attach(ctx, structure)
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, structure: Structure) => InteractionsProvider.attach(ctx, structure, void 0, true),
+        detach: (_, data) => InteractionsProvider.ref(data, false)
     }
 }

+ 3 - 2
src/mol-model-props/computed/themes/accessible-surface-area.ts

@@ -71,7 +71,8 @@ export const AccessibleSurfaceAreaColorThemeProvider: ColorTheme.Provider<Access
     getParams: getAccessibleSurfaceAreaColorThemeParams,
     defaultValues: PD.getDefaultValues(AccessibleSurfaceAreaColorThemeParams),
     isApplicable: (ctx: ThemeDataContext) => !!ctx.structure,
-    ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
-        return data.structure ? AccessibleSurfaceAreaProvider.attach(ctx, data.structure) : Promise.resolve()
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? AccessibleSurfaceAreaProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
+        detach: (_, data) => data.structure && data.structure.customPropertyDescriptors.reference(AccessibleSurfaceAreaProvider.descriptor, false)
     }
 }

+ 3 - 2
src/mol-model-props/computed/themes/interaction-type.ts

@@ -113,7 +113,8 @@ export const InteractionTypeColorThemeProvider: ColorTheme.Provider<InteractionT
     getParams: getInteractionTypeColorThemeParams,
     defaultValues: PD.getDefaultValues(InteractionTypeColorThemeParams),
     isApplicable: (ctx: ThemeDataContext) => !!ctx.structure,
-    ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
-        return data.structure ? InteractionsProvider.attach(ctx, data.structure) : Promise.resolve()
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? InteractionsProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
+        detach: (_, data) => data.structure && data.structure.customPropertyDescriptors.reference(InteractionsProvider.descriptor, false)
     }
 }

+ 4 - 3
src/mol-model-props/integrative/cross-link-restraint/color.ts

@@ -68,7 +68,8 @@ export const CrossLinkColorThemeProvider: ColorTheme.Provider<CrossLinkColorThem
     getParams: getCrossLinkColorThemeParams,
     defaultValues: PD.getDefaultValues(CrossLinkColorThemeParams),
     isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && CrossLinkRestraint.isApplicable(ctx.structure),
-    ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
-        return data.structure ? CrossLinkRestraintProvider.attach(ctx, data.structure) : Promise.resolve()
-    }
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? CrossLinkRestraintProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
+        detach: (_, data) => data.structure && data.structure.customPropertyDescriptors.reference(CrossLinkRestraintProvider.descriptor, false)
+    }    
 }

+ 3 - 2
src/mol-model-props/integrative/cross-link-restraint/representation.ts

@@ -143,7 +143,8 @@ export const CrossLinkRestraintRepresentationProvider: StructureRepresentationPr
     defaultColorTheme: { name: CrossLinkRestraint.Tag.CrossLinkRestraint },
     defaultSizeTheme: { name: 'uniform' },
     isApplicable: (structure: Structure) => CrossLinkRestraint.isApplicable(structure),
-    ensureCustomProperties: (ctx: CustomProperty.Context, structure: Structure) => {
-        return CrossLinkRestraintProvider.attach(ctx, structure)
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, structure: Structure) => CrossLinkRestraintProvider.attach(ctx, structure, void 0, true),
+        detach: (_, data) => CrossLinkRestraintProvider.ref(data, false)
     }
 }

+ 3 - 2
src/mol-model-props/pdbe/themes/structure-quality-report.ts

@@ -102,7 +102,8 @@ export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Param
     },
     defaultValues: PD.getDefaultValues(StructureQualityReportColorThemeParams),
     isApplicable: (ctx: ThemeDataContext) => StructureQualityReport.isApplicable(ctx.structure?.models[0]),
-    ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
-        return data.structure ? StructureQualityReportProvider.attach(ctx, data.structure.models[0]) : Promise.resolve()
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? StructureQualityReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
+        detach: (_, data) => data.structure && data.structure.models[0].customProperties.reference(StructureQualityReportProvider.descriptor, false)
     }
 }

+ 3 - 2
src/mol-model-props/rcsb/representations/validation-report-clashes.ts

@@ -287,7 +287,8 @@ export const ClashesRepresentationProvider: StructureRepresentationProvider<Clas
     defaultColorTheme: { name: 'uniform', props: { value: Color(0xFA28FF) } },
     defaultSizeTheme: { name: 'physical' },
     isApplicable: (structure: Structure) => structure.elementCount > 0,
-    ensureCustomProperties: (ctx: CustomProperty.Context, structure: Structure) => {
-        return ClashesProvider.attach(ctx, structure)
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, structure: Structure) => ClashesProvider.attach(ctx, structure, void 0, true),
+        detach: (_, data) => ClashesProvider.ref(data, false)
     }
 }

+ 3 - 2
src/mol-model-props/rcsb/themes/assembly-symmetry-cluster.ts

@@ -98,7 +98,8 @@ export const AssemblySymmetryClusterColorThemeProvider: ColorTheme.Provider<Asse
     getParams: getAssemblySymmetryClusterColorThemeParams,
     defaultValues: PD.getDefaultValues(AssemblySymmetryClusterColorThemeParams),
     isApplicable: (ctx: ThemeDataContext) => AssemblySymmetry.isApplicable(ctx.structure),
-    ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
-        return data.structure ? AssemblySymmetryProvider.attach(ctx, data.structure) : Promise.resolve()
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? AssemblySymmetryProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
+        detach: (_, data) => data.structure && data.structure.customPropertyDescriptors.reference(AssemblySymmetryProvider.descriptor, false)
     }
 }

+ 3 - 2
src/mol-model-props/rcsb/themes/density-fit.ts

@@ -67,7 +67,8 @@ export const DensityFitColorThemeProvider: ColorTheme.Provider<{}> = {
     getParams: () => ({}),
     defaultValues: PD.getDefaultValues({}),
     isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
-    ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
-        return data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0]) : Promise.resolve()
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
+        detach: (_, data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
     }
 }

+ 3 - 2
src/mol-model-props/rcsb/themes/geometry-quality.ts

@@ -107,7 +107,8 @@ export const GeometryQualityColorThemeProvider: ColorTheme.Provider<GeometricQua
     getParams: getGeometricQualityColorThemeParams,
     defaultValues: PD.getDefaultValues(getGeometricQualityColorThemeParams({})),
     isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
-    ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
-        return data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0]) : Promise.resolve()
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
+        detach: (_, data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
     }
 }

+ 3 - 2
src/mol-model-props/rcsb/themes/random-coil-index.ts

@@ -58,7 +58,8 @@ export const RandomCoilIndexColorThemeProvider: ColorTheme.Provider<{}> = {
     getParams: () => ({}),
     defaultValues: PD.getDefaultValues({}),
     isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
-    ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
-        return data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0]) : Promise.resolve()
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
+        detach: (_, data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
     }
 }

+ 16 - 1
src/mol-model/structure/common/custom-property.ts

@@ -41,6 +41,7 @@ namespace CustomPropertyDescriptor {
 class CustomProperties {
     private _list: CustomPropertyDescriptor[] = [];
     private _set = new Set<CustomPropertyDescriptor>();
+    private _refs = new Map<CustomPropertyDescriptor, number>();
 
     get all(): ReadonlyArray<CustomPropertyDescriptor> {
         return this._list;
@@ -53,7 +54,21 @@ class CustomProperties {
         this._set.add(desc);
     }
 
+    reference(desc: CustomPropertyDescriptor<any>, add: boolean) {
+        let refs = this._refs.get(desc);
+        if (refs === void 0) {
+            refs = 0;
+            this._refs.set(desc, refs);
+        }
+        refs += add ? 1 : -1;
+        this._refs.set(desc, Math.max(refs, 0));
+    }
+
+    hasReference(desc: CustomPropertyDescriptor<any>) {
+        return (this._refs.get(desc) || 0) > 0;
+    }
+
     has(desc: CustomPropertyDescriptor<any>): boolean {
-        return this._set.has(desc);
+        return this._refs.has(desc);
     }
 }

+ 20 - 32
src/mol-plugin-state/actions/structure.ts

@@ -227,21 +227,26 @@ const DownloadStructure = StateAction.build({
 
     await state.transaction(async () => {
         if (downloadParams.length > 0 && asTrajectory) {
-            const traj = await createSingleTrajectoryModel(plugin, state, downloadParams);
-            const struct = createStructure(state.build().to(traj), supportProps, src.params.structure.type);
-            await state.updateTree(struct, { revertIfAborted: true }).runInContext(ctx);
+            const data = await plugin.builders.data.downloadBlob({
+                sources: downloadParams.map((src, i) => ({ id: '' + i, url: src.url, isBinary: src.isBinary })),
+                maxConcurrency: 6
+            }, { state: { isGhost: true } });        
+            const traj = await plugin.builders.structure.parseTrajectory(data, {
+                formats: downloadParams.map((_, i) => ({ id: '' + i, format: 'cif' as 'cif' }))
+            });
+            const model = await plugin.builders.structure.createModel(traj, void 0, supportProps);
+            const struct = await plugin.builders.structure.createStructure(model, src.params.structure.type);
             if (createRepr) {
-                await plugin.builders.structureRepresentation.apply(struct.ref, 'auto');
+                await plugin.builders.representation.structurePreset(struct.ref, 'auto');
             }
         } else {
             for (const download of downloadParams) {
                 const data = await plugin.builders.data.download(download, { state: { isGhost: true } });
-                const traj = createModelTree(state.build().to(data), format);
-
-                const struct = createStructure(traj, supportProps, src.params.structure.type);
-                await state.updateTree(struct, { revertIfAborted: true }).runInContext(ctx);
+                const traj = await plugin.builders.structure.parseTrajectory(data, format);
+                const model = await plugin.builders.structure.createModel(traj, void 0, supportProps);
+                const struct = await plugin.builders.structure.createStructure(model, src.params.structure.type);
                 if (createRepr) {
-                    await plugin.builders.structureRepresentation.apply(struct.ref, 'auto');
+                    await plugin.builders.representation.structurePreset(struct.ref, 'auto');
                 }
             }
         }
@@ -257,23 +262,6 @@ function getDownloadParams(src: string, url: (id: string) => string, label: (id:
     return ret;
 }
 
-async function createSingleTrajectoryModel(plugin: PluginContext, state: State, sources: StateTransformer.Params<Download>[]) {
-    const data = await plugin.builders.data.downloadBlob({
-        sources: sources.map((src, i) => ({ id: '' + i, url: src.url, isBinary: src.isBinary })),
-        maxConcurrency: 6
-    }, { state: { isGhost: true } });
-
-    const trajectory = state.build().to(data)
-        .apply(StateTransforms.Data.ParseBlob, {
-            formats: sources.map((_, i) => ({ id: '' + i, format: 'cif' as 'cif' }))
-        }, { state: { isGhost: true } })
-        .apply(StateTransforms.Model.TrajectoryFromBlob)
-        .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 });
-
-    await plugin.runTask(state.updateTree(trajectory, { revertIfAborted: true }));
-    return trajectory.selector;
-}
-
 export function createModelTree(b: StateBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, format: StructureFormat = 'cif') {
     let parsed: StateBuilder.To<PluginStateObject.Molecule.Trajectory>
     switch (format) {
@@ -314,28 +302,28 @@ function createStructureAndVisuals(ctx: PluginContext, b: StateBuilder.To<Plugin
 export const Create3DRepresentationPreset = StateAction.build({
     display: { name: '3D Representation Preset', description: 'Create one of preset 3D representations.' },
     from: PluginStateObject.Molecule.Structure,
-    isApplicable(a, _, plugin: PluginContext) { return plugin.builders.structureRepresentation.hasProvider(a.data); },
+    isApplicable(a, _, plugin: PluginContext) { return plugin.builders.representation.hasPreset(a.data); },
     params(a, plugin: PluginContext) {
         return {
-            type: plugin.builders.structureRepresentation.getOptions(a.data)
+            type: plugin.builders.representation.getPresets(a.data)
         };
     }
 })(({ ref, params }, plugin: PluginContext) => {
-    plugin.builders.structureRepresentation.apply(ref, params.type.name, params.type.params);
+    plugin.builders.representation.structurePreset(ref, params.type.name, params.type.params);
 });
 
 export const Remove3DRepresentationPreset = StateAction.build({
     display: { name: 'Remove 3D Representation Preset', description: 'Remove 3D representations.' },
     from: PluginStateObject.Molecule.Structure,
-    isApplicable(_, t, plugin: PluginContext) { return plugin.builders.structureRepresentation.hasManagedRepresentation(t.ref); },
+    isApplicable(_, t, plugin: PluginContext) { return plugin.builders.representation.hasPresetRepresentation(t.ref); },
     params(a, plugin: PluginContext) {
         return {
-            type: plugin.builders.structureRepresentation.getOptions(a.data).select
+            type: plugin.builders.representation.getPresets(a.data).select
         };
     }
 })(({ ref, params }, plugin: PluginContext) => {
     // TODO: this will be completely handled by the managed and is just for testing purposes
-    plugin.builders.structureRepresentation.remove(params.type, ref);
+    plugin.builders.representation.removePreset(params.type, ref);
 });
 
 export const UpdateTrajectory = StateAction.build({

+ 0 - 1
src/mol-plugin-state/builder/parser.ts

@@ -1 +0,0 @@
-// TODO: parsing

+ 17 - 14
src/mol-plugin-state/builder/structure-representation.ts → src/mol-plugin-state/builder/representation.ts

@@ -19,28 +19,28 @@ import { UniqueArray } from '../../mol-data/generic';
 // TODO: support quality
 // TODO: support ignore hydrogens
 
-export type StructureRepresentationProviderRef = keyof PresetStructureReprentations | StructureRepresentationProvider | string
+export type RepresentationProviderRef = keyof PresetStructureReprentations | StructureRepresentationProvider | string
 
-export class StructureRepresentationBuilder {
+export class RepresentationBuilder {
     private providers: StructureRepresentationProvider[] = [];
     private providerMap: Map<string, StructureRepresentationProvider> = new Map();
 
     readonly defaultProvider = PresetStructureReprentations.auto;
 
-    private resolveProvider(ref: StructureRepresentationProviderRef) {
+    private resolveProvider(ref: RepresentationProviderRef) {
         return typeof ref === 'string'
             ? PresetStructureReprentations[ref as keyof PresetStructureReprentations] ?? arrayFind(this.providers, p => p.id === ref)
             : ref;
     }
 
-    hasProvider(s: Structure) {
+    hasPreset(s: Structure) {
         for (const p of this.providers) {
             if (!p.isApplicable || p.isApplicable(s, this.plugin)) return true;
         }
         return false;
     }
 
-    getOptions(s: Structure) {
+    getPresets(s: Structure) {
         const options: [string, string][] = [];
         const map: { [K in string]: PD.Any } = Object.create(null);
         for (const p of this.providers) {
@@ -53,7 +53,7 @@ export class StructureRepresentationBuilder {
         return PD.MappedStatic(options[0][0], map, { options });
     }
 
-    hasManagedRepresentation(ref: StateObjectRef) {
+    hasPresetRepresentation(ref: StateObjectRef) {
         // TODO: make this state selection function?
         const tree = this.plugin.state.dataState.tree;
         const root = StateObjectRef.resolve(this.plugin.state.dataState, ref);
@@ -69,7 +69,7 @@ export class StructureRepresentationBuilder {
         }).found;
     }
 
-    getManagedRepresentations(ref: StateObjectRef) {
+    getPresetRepresentations(ref: StateObjectRef) {
         const tree = this.plugin.state.dataState.tree;
         const root = StateObjectRef.resolve(this.plugin.state.dataState, ref);
         if (!root) return [];
@@ -81,7 +81,7 @@ export class StructureRepresentationBuilder {
         }).found.array;
     }
 
-    register(provider: StructureRepresentationProvider) {
+    registerPreset(provider: StructureRepresentationProvider) {
         if (this.providerMap.has(provider.id)) {
             throw new Error(`Repr. provider with id '${provider.id}' already registered.`);
         }
@@ -90,7 +90,7 @@ export class StructureRepresentationBuilder {
         this.providerMap.set(provider.id, provider);
     }
 
-    remove(providerRef: StructureRepresentationProviderRef, structureRoot?: StateObjectRef) {
+    removePreset(providerRef: RepresentationProviderRef, structureRoot?: StateObjectRef) {
         const id = this.resolveProvider(providerRef)?.id;
         if (!id) return;
         
@@ -114,10 +114,10 @@ export class StructureRepresentationBuilder {
         return this.plugin.runTask(state.updateTree(builder));
     }
     
-    apply<K extends keyof PresetStructureReprentations>(parent: StateObjectRef, preset: K, params?: StructureRepresentationProvider.Params<PresetStructureReprentations[K]>): Promise<StructureRepresentationProvider.State<PresetStructureReprentations[K]>> | undefined
-    apply<P = any, S = {}>(parent: StateObjectRef, providers: StructureRepresentationProvider<P, S>, params?: P): Promise<S> | undefined
-    apply(parent: StateObjectRef, providerId: string, params?: any): Promise<any> | undefined
-    apply(parent: StateObjectRef, providerRef: string | StructureRepresentationProvider, params?: any): Promise<any> | undefined {
+    structurePreset<K extends keyof PresetStructureReprentations>(parent: StateObjectRef, preset: K, params?: StructureRepresentationProvider.Params<PresetStructureReprentations[K]>): Promise<StructureRepresentationProvider.State<PresetStructureReprentations[K]>> | undefined
+    structurePreset<P = any, S = {}>(parent: StateObjectRef, providers: StructureRepresentationProvider<P, S>, params?: P): Promise<S> | undefined
+    structurePreset(parent: StateObjectRef, providerId: string, params?: any): Promise<any> | undefined
+    structurePreset(parent: StateObjectRef, providerRef: string | StructureRepresentationProvider, params?: any): Promise<any> | undefined {
         const provider = this.resolveProvider(providerRef);
         if (!provider) return;
 
@@ -137,7 +137,10 @@ export class StructureRepresentationBuilder {
         return this.plugin.runTask(task);
     }
 
+    // TODO
+    // createOrUpdate(component: any, ) { }
+
     constructor(public plugin: PluginContext) {
-        objectForEach(PresetStructureReprentations, r => this.register(r));
+        objectForEach(PresetStructureReprentations, r => this.registerPreset(r));
     }
 }

+ 0 - 1
src/mol-plugin-state/builder/structure-model.ts

@@ -1 +0,0 @@
-// TODO: create managed trajectory / model / structure

+ 134 - 0
src/mol-plugin-state/builder/structure.ts

@@ -0,0 +1,134 @@
+/**
+ * Copyright (c) 2020 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 { StateBuilder, StateObjectRef, StateObjectSelector, StateTransformer } from '../../mol-state';
+import { PluginStateObject as SO } from '../objects';
+import { StateTransforms } from '../transforms';
+import { RootStructureDefinition } from '../helpers/root-structure';
+import { StructureComponentParams } from '../helpers/structure-component';
+
+type TrajectoryFormat = 'pdb' | 'cif' | 'gro' | '3dg'
+
+export enum StructureBuilderTags {
+    Trajectory = 'trajectory',
+    Model = 'model',
+    ModelProperties = 'model-properties',
+    Structure = 'structure',
+    Component = 'structure-component'
+}
+
+export class StructureBuilder {
+    private get dataState() {
+        return this.plugin.state.dataState;
+    }
+
+    private async parseTrajectoryData(data: StateObjectRef<SO.Data.Binary | SO.Data.String>, format: TrajectoryFormat) {
+        const state = this.dataState;
+        const root = state.build().to(data);
+        let parsed: StateBuilder.To<SO.Molecule.Trajectory>;
+        const tag = { tags: StructureBuilderTags.Trajectory };
+        switch (format) {
+            case 'cif':
+                parsed = root.apply(StateTransforms.Data.ParseCif, void 0, { state: { isGhost: true } })
+                    .apply(StateTransforms.Model.TrajectoryFromMmCif, void 0, tag)
+                break
+            case 'pdb':
+                parsed = root.apply(StateTransforms.Model.TrajectoryFromPDB, void 0, tag);
+                break
+            case 'gro':
+                parsed = root.apply(StateTransforms.Model.TrajectoryFromGRO, void 0, tag);
+                break
+            case '3dg':
+                parsed = root.apply(StateTransforms.Model.TrajectoryFrom3DG, void 0, tag);
+                break
+            default:
+                throw new Error('unsupported format')
+        }
+
+        await this.plugin.runTask(this.dataState.updateTree(parsed, { revertOnError: true }));
+
+        return parsed.selector;
+    }
+
+    private async parseTrajectoryBlob(data: StateObjectRef<SO.Data.Blob>, params: StateTransformer.Params<StateTransforms['Data']['ParseBlob']>) {
+        const state = this.dataState;
+        const trajectory = state.build().to(data)
+            .apply(StateTransforms.Data.ParseBlob, params, { state: { isGhost: true } })
+            .apply(StateTransforms.Model.TrajectoryFromBlob, void 0, { tags: StructureBuilderTags.Trajectory });        
+        await this.plugin.runTask(this.dataState.updateTree(trajectory, { revertOnError: true }));
+        return trajectory.selector;
+    }
+
+    async parseTrajectory(data: StateObjectRef<SO.Data.Binary | SO.Data.String>, format: TrajectoryFormat): Promise<StateObjectSelector<SO.Molecule.Trajectory>>
+    async parseTrajectory(blob: StateObjectRef<SO.Data.Blob>, params: StateTransformer.Params<StateTransforms['Data']['ParseBlob']>): Promise<StateObjectSelector<SO.Molecule.Trajectory>>
+    async parseTrajectory(data: StateObjectRef, params: any) {
+        // TODO: proper format support
+        // needs to integrated to transforms directly because of blobs
+        const cell = StateObjectRef.resolveAndCheck(this.dataState, data as StateObjectRef);
+        if (!cell) throw new Error('Invalid data cell.');
+
+        if (SO.Data.Blob.is(cell.obj)) {
+            return this.parseTrajectoryBlob(data, params);
+        } else {
+            return this.parseTrajectoryData(data, params);
+        }
+    }
+
+    async createModel(trajectory: StateObjectRef<SO.Molecule.Trajectory>, params?: StateTransformer.Params<StateTransforms['Model']['ModelFromTrajectory']>, supportProps?: boolean) {
+        const state = this.dataState;
+        if (supportProps) {
+            const model = state.build().to(trajectory)
+                .apply(StateTransforms.Model.ModelFromTrajectory, params || { modelIndex: 0 })
+                .apply(StateTransforms.Model.CustomModelProperties, void 0, { tags: [StructureBuilderTags.Model, StructureBuilderTags.ModelProperties] });
+            await this.plugin.runTask(this.dataState.updateTree(model, { revertOnError: true }));
+            return model.selector;
+        } else {
+            const model = state.build().to(trajectory)
+                .apply(StateTransforms.Model.ModelFromTrajectory, params || { modelIndex: 0 }, { tags: StructureBuilderTags.Model });        
+            await this.plugin.runTask(this.dataState.updateTree(model, { revertOnError: true }));
+            return model.selector;
+        }
+    }
+
+    async createStructure(model: StateObjectRef<SO.Molecule.Model>, params?: RootStructureDefinition.Params) {
+        const state = this.dataState;
+        const structure = state.build().to(model)
+            .apply(StateTransforms.Model.StructureFromModel, { type: params || { name: 'assembly', params: { } } }, { tags: StructureBuilderTags.Structure });        
+        await this.plugin.runTask(this.dataState.updateTree(structure, { revertOnError: true }));
+        return structure.selector;
+    }
+
+    /** returns undefined if the component is empty/null */
+    async tryCreateComponent(structure: StateObjectRef<SO.Molecule.Structure>, params: StructureComponentParams, tag?: string): Promise<StateObjectRef<SO.Molecule.Structure> | undefined> {
+        const state = this.dataState;
+
+        const root = state.build().to(structure);
+        let component: StateBuilder.To<SO.Molecule.Structure>;
+
+        if (tag) {
+            const typeTag = `structure-component-${tag}`;
+            component = root.applyOrUpdateTagged(typeTag, StateTransforms.Model.StructureComponent, params, { tags: [StructureBuilderTags.Component, typeTag] });
+        } else {
+            component = root.apply(StateTransforms.Model.StructureComponent, params, { tags: StructureBuilderTags.Component });
+        }
+
+        await this.plugin.runTask(this.dataState.updateTree(component));
+
+        const selector = component.selector;
+
+        if (!selector.isOk || selector.cell?.obj?.data.elementCount === 0) {
+            const del = state.build().delete(selector.ref);
+            await this.plugin.runTask(this.dataState.updateTree(del));
+            return;
+        }
+
+        return selector;
+    }
+
+    constructor(public plugin: PluginContext) {
+    }
+}

+ 82 - 85
src/mol-plugin-state/builder/structure/preset.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  */
@@ -9,9 +9,10 @@ import { StructureRepresentation3DHelpers } from '../../transforms/representatio
 import { StructureSelectionQueries as Q } from '../../../mol-plugin/util/structure-selection-helper';
 import { BuiltInStructureRepresentations } from '../../../mol-repr/structure/registry';
 import { StructureRepresentationProvider, RepresentationProviderTags } from './provider';
-import { StateBuilder } from '../../../mol-state';
+import { StateObjectRef } from '../../../mol-state';
 import { PluginStateObject } from '../../objects';
 import { StaticStructureComponentType } from '../../helpers/structure-component';
+import { PluginContext } from '../../../mol-plugin/context';
 
 const auto = StructureRepresentationProvider({
     id: 'preset-structure-representation-auto',
@@ -39,46 +40,43 @@ const defaultPreset = StructureRepresentationProvider({
     id: 'preset-structure-representation-default',
     display: { name: 'Default', group: 'Preset' },
     async apply(ctx, state, structureCell, _, plugin) {
-        const root = state.build().to(structureCell.transform.ref);
-        const structure = structureCell.obj!.data;
-
+        const structure = structureCell.obj?.data!;
         const reprTags = [this.id, RepresentationProviderTags.Representation];
-
-        applyComplex(root, 'protein-or-nucleic')
-            .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D,
-                StructureRepresentation3DHelpers.getDefaultParams(plugin, 'cartoon', structure));
-
-        const ligand = applyComplex(root, 'ligand');
-        const ligandRepr = ligand.applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D,
-            StructureRepresentation3DHelpers.getDefaultParams(plugin, 'ball-and-stick', structure));
-
-        applyComplex(root, 'non-standard')
-            .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D,
-                StructureRepresentation3DHelpers.getDefaultParamsWithTheme(plugin, 'ball-and-stick', 'polymer-id', structure, void 0));
-
-        const branched = applyComplex(root, 'branched');
-
-        branched.applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D,
-            StructureRepresentation3DHelpers.getDefaultParams(plugin, 'ball-and-stick', structure, { alpha: 0.15 }));
-        branched.applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D,
-            StructureRepresentation3DHelpers.getDefaultParams(plugin, 'carbohydrate', structure));
-
-        applyComplex(root, 'water')
-            .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D,
-                StructureRepresentation3DHelpers.getDefaultParams(plugin, 'ball-and-stick', structure, { alpha: 0.51 }));
-
-        applyComplex(root, 'coarse')
-            .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D,
-                StructureRepresentation3DHelpers.getDefaultParamsWithTheme(plugin, 'spacefill', 'polymer-id', structure, {}));
-
-        await state.updateTree(root, { revertOnError: true }).runInContext(ctx);
-
-        return {
-            ligand: {
-                selection: ligand.selector,
-                repr: ligandRepr.selector
-            }
+        
+        const components = {
+            proteinOrNucleic: await staticComponent(plugin, structureCell, 'protein-or-nucleic'),
+            ligand: await staticComponent(plugin, structureCell, 'ligand'),
+            nonStandard: await staticComponent(plugin, structureCell, 'non-standard'),
+            branched: await staticComponent(plugin, structureCell, 'branched'),
+            water: await staticComponent(plugin, structureCell, 'water'),
+            coarse: await staticComponent(plugin, structureCell, 'coarse')
         };
+                
+        const builder = state.build();
+        const representations = {
+            proteinOrNucleic: components.proteinOrNucleic && builder
+                .to(components.proteinOrNucleic)
+                .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D, StructureRepresentation3DHelpers.getDefaultParams(plugin, 'cartoon', structure)).selector,
+            ligand: components.ligand && builder
+                .to(components.ligand)
+                .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D, StructureRepresentation3DHelpers.getDefaultParams(plugin, 'ball-and-stick', structure)).selector,
+            nonStandard: components.nonStandard && builder
+                .to(components.nonStandard)
+                .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D, StructureRepresentation3DHelpers.getDefaultParamsWithTheme(plugin, 'ball-and-stick', 'polymer-id', structure, void 0)).selector,
+            branched: components.branched && {
+                ballAndStick: builder.to(components.branched).applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D, StructureRepresentation3DHelpers.getDefaultParams(plugin, 'ball-and-stick', structure, { alpha: 0.15 })).selector,
+                snfg3d: builder.to(components.branched).applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D, StructureRepresentation3DHelpers.getDefaultParams(plugin, 'carbohydrate', structure)).selector
+            },
+            water: components.water && builder
+                .to(components.water)
+                .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D, StructureRepresentation3DHelpers.getDefaultParams(plugin, 'ball-and-stick', structure, { alpha: 0.51 })).selector,
+            coarse: components.coarse && builder
+                .to(components.coarse)
+                .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D, StructureRepresentation3DHelpers.getDefaultParamsWithTheme(plugin, 'spacefill', 'polymer-id', structure, {}))
+        };
+        
+        await state.updateTree(builder, { revertOnError: false }).runInContext(ctx);
+        return { components, representations };
     }
 });
 
@@ -86,20 +84,26 @@ const proteinAndNucleic = StructureRepresentationProvider({
     id: 'preset-structure-representation-protein-and-nucleic',
     display: { name: 'Protein & Nucleic', group: 'Preset' },
     async apply(ctx, state, structureCell, _, plugin) {
-        const root = plugin.state.dataState.build().to(structureCell.transform.ref);
         const structure = structureCell.obj!.data;
         const reprTags = [this.id, RepresentationProviderTags.Representation];
 
-        applySelection(root, 'protein')
-            .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D,
-                StructureRepresentation3DHelpers.getDefaultParams(plugin, 'cartoon', structure));
-
-        applySelection(root, 'nucleic')
-            .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D,
-                StructureRepresentation3DHelpers.getDefaultParams(plugin, 'gaussian-surface', structure));
+        const components = {
+            protein: await selectionComponent(plugin, structureCell, 'protein'),
+            nucleic: await selectionComponent(plugin, structureCell, 'nucleic'),
+        };
 
-        await state.updateTree(root, { revertOnError: true }).runInContext(ctx);
-        return {};
+        const builder = state.build();
+        const representations = {
+            protein: components.protein && builder
+                .to(components.protein)
+                .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D, StructureRepresentation3DHelpers.getDefaultParams(plugin, 'cartoon', structure)).selector,
+            nucleic: components.nucleic && builder
+                .to(components.nucleic)
+                .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D, StructureRepresentation3DHelpers.getDefaultParams(plugin, 'gaussian-surface', structure)).selector,
+        };
+      
+        await state.updateTree(builder, { revertOnError: true }).runInContext(ctx);
+        return { components, representations };
     }
 });
 
@@ -116,11 +120,19 @@ const capsid = StructureRepresentationProvider({
 
         const reprTags = [this.id, RepresentationProviderTags.Representation];
 
-        applySelection(root, 'polymer')
-            .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D, params);
+        const components = {
+            polymer: await selectionComponent(plugin, structureCell, 'polymer')
+        };
+
+        const builder = state.build();
+        const representations = {
+            polymer: components.polymer && builder
+                .to(components.polymer)
+                .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D, params).selector,
+        };
 
         await state.updateTree(root, { revertOnError: true }).runInContext(ctx);
-        return {};
+        return { components, representations };
     }
 });
 
@@ -140,50 +152,36 @@ const coarseCapsid = StructureRepresentationProvider({
 
         const reprTags = [this.id, RepresentationProviderTags.Representation];
 
-        applySelection(root, 'trace')
-            .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D, params);
-
-        await state.updateTree(root, { revertOnError: true }).runInContext(ctx);
-        return {};
-    }
-});
-
-const cartoon = StructureRepresentationProvider({
-    id: 'preset-structure-representation-cartoon',
-    display: { name: 'Cartoon', group: 'Preset' },
-    async apply(ctx, state, structureCell, _, plugin) {
-        const root = plugin.state.dataState.build().to(structureCell.transform.ref);
-        const structure = structureCell.obj!.data;
-
-        const params = StructureRepresentation3DHelpers.createParams(plugin, structure, {
-            repr: BuiltInStructureRepresentations['cartoon']
-        });
-
-        const reprTags = [this.id, RepresentationProviderTags.Representation];
+        const components = {
+            trace: await selectionComponent(plugin, structureCell, 'trace')
+        };
 
-        applySelection(root, 'polymer')
-            .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D, params);
+        const builder = state.build();
+        const representations = {
+            polymer: components.trace && builder
+                .to(components.trace)
+                .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D, params).selector,
+        };
 
         await state.updateTree(root, { revertOnError: true }).runInContext(ctx);
-        return {};
+        return { components, representations };
     }
 });
 
-function applyComplex(to: StateBuilder.To<PluginStateObject.Molecule.Structure>, type: StaticStructureComponentType) {
-    return to.applyOrUpdateTagged(type, StateTransforms.Model.StructureComponent, { 
+function staticComponent(plugin: PluginContext, structure: StateObjectRef<PluginStateObject.Molecule.Structure>, type: StaticStructureComponentType) {
+    return plugin.builders.structure.tryCreateComponent(structure, { 
         type: { name: 'static', params: type },
         nullIfEmpty: true,
         label: ''
-    }, { tags: RepresentationProviderTags.Selection });
+    }, `static-${type}`);
 }
 
-function applySelection(to: StateBuilder.To<PluginStateObject.Molecule.Structure>, query: keyof typeof Q) {
-    return to.applyOrUpdateTagged(query, StateTransforms.Model.StructureComponent, { 
+function selectionComponent(plugin: PluginContext, structure: StateObjectRef<PluginStateObject.Molecule.Structure>, query: keyof typeof Q) {
+    return plugin.builders.structure.tryCreateComponent(structure, { 
         type: { name: 'expression', params: Q[query].expression },
         nullIfEmpty: true,
         label: Q[query].label
-    },
-    { tags: RepresentationProviderTags.Selection });
+    }, `selection-${query}`);
 }
 
 export const PresetStructureReprentations = {
@@ -191,7 +189,6 @@ export const PresetStructureReprentations = {
     default: defaultPreset,
     proteinAndNucleic,
     capsid,
-    coarseCapsid,
-    cartoon
+    coarseCapsid
 };
 export type PresetStructureReprentations = typeof PresetStructureReprentations;

+ 2 - 2
src/mol-plugin-state/helpers/structure-component.ts

@@ -39,10 +39,10 @@ export const StructureComponentParams = {
         bundle: PD.Value<StructureElement.Bundle>(StructureElement.Bundle.Empty),
         script: PD.Script({ language: 'mol-script', expression: '(sel.atom.all)' }),
     }, { isHidden: true }),
-    nullIfEmpty: PD.Boolean(true, { isHidden: true }),
+    nullIfEmpty: PD.Optional(PD.Boolean(true, { isHidden: true })),
     label: PD.Text('', { isHidden: true })
 };
-export type StructureComponentParams = PD.Values<typeof StructureComponentParams>
+export type StructureComponentParams = PD.ValuesFor<typeof StructureComponentParams>
 
 export function createStructureComponent(a: Structure, params: StructureComponentParams, cache: { source: Structure, entry?: StructureQueryHelper.CacheEntry }) {
     cache.source = a;

+ 3 - 1
src/mol-plugin-state/transforms.ts

@@ -16,4 +16,6 @@ export const StateTransforms = {
     Model,
     Volume,
     Representation
-}
+}
+
+export type StateTransforms = typeof StateTransforms

+ 24 - 2
src/mol-plugin-state/transforms/model.ts

@@ -701,6 +701,17 @@ const CustomModelProperties = PluginStateTransform.BuiltIn({
             await attachModelProps(a.data, ctx, taskCtx, params);
             return new SO.Molecule.Model(a.data, { label: 'Model Props' });
         });
+    },
+    update({ a, oldParams, newParams }, ctx: PluginContext) {
+        return Task.create('Custom Props', async taskCtx => {
+            for (const name of oldParams.autoAttach) {
+                const property = ctx.customModelProperties.get(name);
+                if (!property) continue;
+                a.data.customProperties.reference(property.descriptor, false);
+            }
+            await attachModelProps(a.data, ctx, taskCtx, newParams);
+            return StateTransformer.UpdateResult.Updated;
+        });
     }
 });
 async function attachModelProps(model: Model, ctx: PluginContext, taskCtx: RuntimeContext, params: ReturnType<CustomModelProperties['createDefaultParams']>) {
@@ -711,7 +722,7 @@ async function attachModelProps(model: Model, ctx: PluginContext, taskCtx: Runti
         const props = properties[name]
         if (autoAttach.includes(name)) {
             try {
-                await property.attach(propertyCtx, model, props)
+                await property.attach(propertyCtx, model, props, true)
             } catch (e) {
                 ctx.log.warn(`Error attaching model prop '${name}': ${e}`);
             }
@@ -736,6 +747,17 @@ const CustomStructureProperties = PluginStateTransform.BuiltIn({
             await attachStructureProps(a.data, ctx, taskCtx, params);
             return new SO.Molecule.Structure(a.data, { label: 'Structure Props' });
         });
+    },
+    update({ a, oldParams, newParams }, ctx: PluginContext) {
+        return Task.create('Custom Props', async taskCtx => {
+            for (const name of oldParams.autoAttach) {
+                const property = ctx.customModelProperties.get(name);
+                if (!property) continue;
+                a.data.customPropertyDescriptors.reference(property.descriptor, false);
+            }
+            await attachStructureProps(a.data, ctx, taskCtx, newParams);
+            return StateTransformer.UpdateResult.Updated;
+        });
     }
 });
 async function attachStructureProps(structure: Structure, ctx: PluginContext, taskCtx: RuntimeContext, params: ReturnType<CustomStructureProperties['createDefaultParams']>) {
@@ -746,7 +768,7 @@ async function attachStructureProps(structure: Structure, ctx: PluginContext, ta
         const props = properties[name]
         if (autoAttach.includes(name)) {
             try {
-                await property.attach(propertyCtx, structure, props)
+                await property.attach(propertyCtx, structure, props, true)
             } catch (e) {
                 ctx.log.warn(`Error attaching structure prop '${name}': ${e}`);
             }

+ 17 - 7
src/mol-plugin-state/transforms/representation.ts

@@ -15,7 +15,7 @@ import { BuiltInVolumeRepresentationsName } from '../../mol-repr/volume/registry
 import { VolumeParams } from '../../mol-repr/volume/representation';
 import { StateTransformer, StateObject } from '../../mol-state';
 import { Task } from '../../mol-task';
-import { BuiltInColorThemeName, ColorTheme, BuiltInColorThemes } from '../../mol-theme/color';
+import { BuiltInColorThemeName, ColorTheme } from '../../mol-theme/color';
 import { BuiltInSizeThemeName, SizeTheme } from '../../mol-theme/size';
 import { Theme, ThemeRegistryContext } from '../../mol-theme/theme';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
@@ -210,7 +210,7 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
         return Task.create('Structure Representation', async ctx => {
             const propertyCtx = { runtime: ctx, fetch: plugin.fetch }
             const provider = plugin.structureRepresentation.registry.get(params.type.name)
-            if (provider.ensureCustomProperties) await provider.ensureCustomProperties(propertyCtx, a.data)
+            if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data)
             const props = params.type.params || {}
             const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.structureRepresentation.themeCtx }, provider.getParams)
             await Theme.ensureDependencies(propertyCtx, plugin.structureRepresentation.themeCtx, { structure: a.data }, params)
@@ -222,10 +222,14 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
     },
     update({ a, b, oldParams, newParams }, plugin: PluginContext) {
         return Task.create('Structure Representation', async ctx => {
-            if (newParams.type.name !== oldParams.type.name) return StateTransformer.UpdateResult.Recreate;
+            const oldProvider = plugin.structureRepresentation.registry.get(oldParams.type.name);
             const propertyCtx = { runtime: ctx, fetch: plugin.fetch }
+            if (oldProvider.ensureCustomProperties) oldProvider.ensureCustomProperties.detach(propertyCtx, a.data);
+            Theme.releaseDependencies(propertyCtx, plugin.structureRepresentation.themeCtx, { structure: a.data }, oldParams);
+
+            if (newParams.type.name !== oldParams.type.name) return StateTransformer.UpdateResult.Recreate;
             const provider = plugin.structureRepresentation.registry.get(newParams.type.name)
-            if (provider.ensureCustomProperties) await provider.ensureCustomProperties(propertyCtx, a.data)
+            if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data)
             const props = { ...b.data.repr.props, ...newParams.type.params }
             await Theme.ensureDependencies(propertyCtx, plugin.structureRepresentation.themeCtx, { structure: a.data }, newParams)
             b.data.repr.setTheme(Theme.create(plugin.structureRepresentation.themeCtx, { structure: a.data }, newParams));
@@ -238,7 +242,6 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
         if (src.colorTheme.name !== 'uniform' || tar.colorTheme.name !== 'uniform') {
             return t <= 0.5 ? src : tar;
         }
-        BuiltInColorThemes
         const from = src.colorTheme.params.value as Color, to = tar.colorTheme.params.value as Color;
         const value = Color.interpolate(from, to, t);
         return {
@@ -587,7 +590,7 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
         return Task.create('Volume Representation', async ctx => {
             const propertyCtx = { runtime: ctx, fetch: plugin.fetch }
             const provider = plugin.volumeRepresentation.registry.get(params.type.name)
-            if (provider.ensureCustomProperties) await provider.ensureCustomProperties(propertyCtx, a.data)
+            if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data)
             const props = params.type.params || {}
             const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.volumeRepresentation.themeCtx }, provider.getParams)
             repr.setTheme(Theme.create(plugin.volumeRepresentation.themeCtx, { volume: a.data }, params))
@@ -598,7 +601,14 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
     },
     update({ a, b, oldParams, newParams }, plugin: PluginContext) {
         return Task.create('Volume Representation', async ctx => {
-            if (newParams.type.name !== oldParams.type.name) return StateTransformer.UpdateResult.Recreate;
+            if (newParams.type.name !== oldParams.type.name) {
+                const oldProvider = plugin.volumeRepresentation.registry.get(oldParams.type.name);
+                if (oldProvider.ensureCustomProperties) {
+                    const propertyCtx = { runtime: ctx, fetch: plugin.fetch }
+                    oldProvider.ensureCustomProperties.detach(propertyCtx, a.data)
+                }
+                return StateTransformer.UpdateResult.Recreate;
+            }
             const props = { ...b.data.repr.props, ...newParams.type.params }
             b.data.repr.setTheme(Theme.create(plugin.volumeRepresentation.themeCtx, { volume: a.data }, newParams))
             await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);

+ 3 - 2
src/mol-plugin/behavior/dynamic/custom-props/computed/accessible-surface-area.ts

@@ -64,7 +64,7 @@ function accessibleSurfaceAreaLabel(loci: Loci): string | undefined {
         if (loci.elements.length === 0) return;
 
         const accessibleSurfaceArea = AccessibleSurfaceAreaProvider.get(loci.structure).value
-        if (!accessibleSurfaceArea) return;
+        if (!accessibleSurfaceArea || loci.structure.customPropertyDescriptors.hasReference(AccessibleSurfaceAreaProvider.descriptor)) return;
 
         const { getSerialIndex } = loci.structure.root.serialMapping
         const { area, serialResidueIndex } = accessibleSurfaceArea
@@ -73,6 +73,7 @@ function accessibleSurfaceAreaLabel(loci: Loci): string | undefined {
 
         for (const { indices, unit } of loci.elements) {
             const { elements } = unit
+
             OrderedSet.forEach(indices, idx => {
                 const rSI = serialResidueIndex[getSerialIndex(unit, elements[idx])]
                 if (rSI !== -1 && !seen.has(rSI)) {
@@ -88,7 +89,7 @@ function accessibleSurfaceAreaLabel(loci: Loci): string | undefined {
 
     } else if(loci.kind === 'structure-loci') {
         const accessibleSurfaceArea = AccessibleSurfaceAreaProvider.get(loci.structure).value
-        if (!accessibleSurfaceArea) return;
+        if (!accessibleSurfaceArea || loci.structure.customPropertyDescriptors.hasReference(AccessibleSurfaceAreaProvider.descriptor)) return;
 
         return `Accessible Surface Area <small>(Whole Structure)</small>: ${arraySum(accessibleSurfaceArea.area).toFixed(2)} \u212B<sup>2</sup>`;
     }

+ 2 - 2
src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts

@@ -25,13 +25,13 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo
 
         private labelPDBeValidation = (loci: Loci): string | undefined => {
             if (!this.params.showTooltip) return void 0;
-
+            
             switch (loci.kind) {
                 case 'element-loci':
                     if (loci.elements.length === 0) return void 0;
                     const e = loci.elements[0];
                     const u = e.unit;
-                    if (!u.model.customProperties.has(StructureQualityReportProvider.descriptor)) return void 0;
+                    if (!u.model.customProperties.has(StructureQualityReportProvider.descriptor) || !u.model.customProperties.hasReference(StructureQualityReportProvider.descriptor)) return void 0;
 
                     const se = StructureElement.Location.create(loci.structure, u, u.elements[OrderedSet.getAt(e.indices, 0)]);
                     const issues = StructureQualityReport.getIssues(se);

+ 3 - 0
src/mol-plugin/behavior/dynamic/custom-props/rcsb/validation-report.ts

@@ -90,6 +90,7 @@ function geometryQualityLabel(loci: Loci): string | undefined {
 
             const validationReport = ValidationReportProvider.get(unit.model).value
             if (!validationReport) return
+            if (unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) return
 
             const { bondOutliers, angleOutliers } = validationReport
             const eI = unit.elements[OrderedSet.start(indices)]
@@ -170,6 +171,7 @@ function densityFitLabel(loci: Loci): string | undefined {
         for (const { indices, unit } of loci.elements) {
             const validationReport = ValidationReportProvider.get(unit.model).value
             if (!validationReport) continue
+            if (unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) continue
 
             const { rsrz, rscc } = validationReport
             const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index
@@ -226,6 +228,7 @@ function randomCoilIndexLabel(loci: Loci): string | undefined {
         for (const { indices, unit } of loci.elements) {
             const validationReport = ValidationReportProvider.get(unit.model).value
             if (!validationReport) continue
+            if (unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) continue
 
             const { rci } = validationReport
             const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index

+ 5 - 3
src/mol-plugin/context.ts

@@ -43,10 +43,11 @@ import { StructureOverpaintHelper } from './util/structure-overpaint-helper';
 import { PluginToastManager } from './util/toast';
 import { StructureMeasurementManager } from './util/structure-measurement';
 import { ViewportScreenshotHelper } from './util/viewport-screenshot';
-import { StructureRepresentationBuilder } from '../mol-plugin-state/builder/structure-representation';
+import { RepresentationBuilder } from '../mol-plugin-state/builder/representation';
 import { CustomProperty } from '../mol-model-props/common/custom-property';
 import { PluginConfigManager } from './config';
 import { DataBuilder } from '../mol-plugin-state/builder/data';
+import { StructureBuilder } from '../mol-plugin-state/builder/structure';
 
 export class PluginContext {
     private disposed = false;
@@ -126,7 +127,8 @@ export class PluginContext {
 
     readonly builders = {
         data: new DataBuilder(this),
-        structureRepresentation: void 0 as any as StructureRepresentationBuilder
+        structure: new StructureBuilder(this),
+        representation: void 0 as any as RepresentationBuilder
     };
 
     readonly customModelProperties = new CustomProperty.Registry<Model>();
@@ -282,7 +284,7 @@ export class PluginContext {
         this.interactivity = new Interactivity(this);
         this.lociLabels = new LociLabelManager(this);
 
-        (this.builders.structureRepresentation as StructureRepresentationBuilder)= new StructureRepresentationBuilder(this);
+        (this.builders.representation as RepresentationBuilder)= new RepresentationBuilder(this);
 
         this.log.message(`Mol* Plugin ${PLUGIN_VERSION} [${PLUGIN_VERSION_DATE.toLocaleString()}]`);
         if (!isProductionMode) this.log.message(`Development mode enabled`);

+ 4 - 1
src/mol-repr/representation.ts

@@ -48,7 +48,10 @@ export interface RepresentationProvider<D, P extends PD.Params, S extends Repres
     readonly defaultColorTheme: { name: string, props?: {} }
     readonly defaultSizeTheme: { name: string, props?: {} }
     readonly isApplicable: (data: D) => boolean
-    readonly ensureCustomProperties?: (ctx: CustomProperty.Context, data: D) => Promise<void>
+    readonly ensureCustomProperties?: {
+        attach: (ctx: CustomProperty.Context, data: D) => Promise<void>,
+        detach: (ctx: CustomProperty.Context, data: D) => void
+    }
 }
 
 export namespace RepresentationProvider {

+ 3 - 2
src/mol-repr/structure/representation/cartoon.ts

@@ -64,7 +64,8 @@ export const CartoonRepresentationProvider: StructureRepresentationProvider<Cart
     defaultColorTheme: { name: 'polymer-id' },
     defaultSizeTheme: { name: 'uniform' },
     isApplicable: (structure: Structure) => structure.polymerResidueCount > 0,
-    ensureCustomProperties: (ctx: CustomProperty.Context, structure: Structure) => {
-        return SecondaryStructureProvider.attach(ctx, structure)
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, structure: Structure) => SecondaryStructureProvider.attach(ctx, structure, void 0, true),
+        detach: (_, data) => SecondaryStructureProvider.ref(data, false)
     }
 }

+ 2 - 1
src/mol-state/object.ts

@@ -154,12 +154,13 @@ export class StateObjectSelector<S extends StateObject = StateObject, T extends
         }
         if (cell.status === 'ok') return true;
         if (cell.status === 'error') throw new Error(cell.errorText);
+        if (cell.obj === StateObject.Null) throw new Error('The object is Null.');
         throw new Error(`Unresolved. Did you await/then the corresponding state update?`);
     }
 
     get isOk() {
         const cell = this.cell;
-        return cell && cell.status === 'ok';
+        return cell && cell.status === 'ok' && cell.obj !== StateObject.Null;
     }
 
     constructor(public ref: StateTransform.Ref, public state?: State) {

+ 2 - 1
src/mol-state/state/builder.ts

@@ -6,7 +6,7 @@
 
 import { StateTree } from '../tree/immutable';
 import { TransientTree } from '../tree/transient';
-import { StateObject, StateObjectCell, StateObjectSelector } from '../object';
+import { StateObject, StateObjectCell, StateObjectSelector, StateObjectRef } from '../object';
 import { StateTransform } from '../transform';
 import { StateTransformer } from '../transformer';
 import { State } from '../state';
@@ -80,6 +80,7 @@ namespace StateBuilder {
         get currentTree() { return this.state.tree; }
 
         to<A extends StateObject, T extends StateTransformer>(ref: StateTransform.Ref): To<A, T>
+        to<A extends StateObject, T extends StateTransformer>(ref: StateObjectRef<A>): To<A, T>
         to<C extends StateObjectCell>(cell: C): To<StateObjectCell.Obj<C>, StateObjectCell.Transformer<C>>
         to<S extends StateObjectSelector>(selector: S): To<StateObjectSelector.Obj<S>, StateObjectSelector.Transformer<S>>
         to(refOrCellOrSelector: StateTransform.Ref | StateObjectCell | StateObjectSelector) {

+ 4 - 3
src/mol-theme/color/secondary-structure.ts

@@ -118,7 +118,8 @@ export const SecondaryStructureColorThemeProvider: ColorTheme.Provider<Secondary
     getParams: getSecondaryStructureColorThemeParams,
     defaultValues: PD.getDefaultValues(SecondaryStructureColorThemeParams),
     isApplicable: (ctx: ThemeDataContext) => !!ctx.structure,
-    ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
-        return data.structure ? SecondaryStructureProvider.attach(ctx, data.structure) : Promise.resolve()
-    }
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? SecondaryStructureProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
+        detach: (_, data) => data.structure && data.structure.customPropertyDescriptors.reference(SecondaryStructureProvider.descriptor, false)
+    }    
 }

+ 11 - 3
src/mol-theme/theme.ts

@@ -52,8 +52,13 @@ namespace Theme {
     }
 
     export async function ensureDependencies(ctx: CustomProperty.Context, theme: ThemeRegistryContext, data: ThemeDataContext, props: Props) {
-        await theme.colorThemeRegistry.get(props.colorTheme.name).ensureCustomProperties?.(ctx, data)
-        await theme.sizeThemeRegistry.get(props.sizeTheme.name).ensureCustomProperties?.(ctx, data)
+        await theme.colorThemeRegistry.get(props.colorTheme.name).ensureCustomProperties?.attach(ctx, data)
+        await theme.sizeThemeRegistry.get(props.sizeTheme.name).ensureCustomProperties?.attach(ctx, data)
+    }
+
+    export function releaseDependencies(ctx: CustomProperty.Context, theme: ThemeRegistryContext, data: ThemeDataContext, props: Props) {
+        theme.colorThemeRegistry.get(props.colorTheme.name).ensureCustomProperties?.detach(ctx, data)
+        theme.sizeThemeRegistry.get(props.sizeTheme.name).ensureCustomProperties?.detach(ctx, data)
     }
 }
 
@@ -66,7 +71,10 @@ export interface ThemeProvider<T extends ColorTheme<P> | SizeTheme<P>, P extends
     readonly getParams: (ctx: ThemeDataContext) => P
     readonly defaultValues: PD.Values<P>
     readonly isApplicable: (ctx: ThemeDataContext) => boolean
-    readonly ensureCustomProperties?: (ctx: CustomProperty.Context, data: ThemeDataContext) => Promise<void>
+    readonly ensureCustomProperties?: {
+        attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => Promise<void>,
+        detach: (ctx: CustomProperty.Context, data: ThemeDataContext) => void
+    }
 }
 
 function getTypes(list: { name: string, provider: ThemeProvider<any, any> }[]) {

+ 7 - 0
src/mol-util/param-definition.ts

@@ -279,6 +279,8 @@ export namespace ParamDefinition {
 
     export type Params = { [k: string]: Any }
     export type Values<T extends Params> = { [k in keyof T]: T[k]['defaultValue'] }
+    /** This is required for params with optional values */
+    export type ValuesFor<T extends For<any>> = Normalize<{ [k in keyof T]: T[k]['defaultValue'] }>
 
     type Optionals<P> = { [K in keyof P]-?: undefined extends P[K] ? K : never }[keyof P]
     type NonOptionals<P> = { [K in keyof P]-?: undefined extends P[K] ? never: K }[keyof P]
@@ -286,6 +288,11 @@ export namespace ParamDefinition {
     export type For<P> = { [K in keyof P]-?: Base<P[K]> }
     export type Def<P> = { [K in keyof P]: Any }
 
+
+    export function For<P>(params: For<P>): For<P> {
+        return 0 as any;
+    }
+
     export function getDefaultValues<T extends Params>(params: T) {
         const d: { [k: string]: any } = {}
         for (const k of Object.keys(params)) {