Ver Fonte

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

Alexander Rose há 6 anos atrás
pai
commit
ef32f877d5

+ 6 - 0
README.md

@@ -122,6 +122,12 @@ Run the image
 
     export NODE_PATH="build/src"; node build/state-docs
 
+**Convert any CIF to BinaryCIF**
+
+    node build/model-server/preprocess -i file.cif -ob file.bcif
+
+To see all available commands, use ``node build/model-server/preprocess -h``.
+
 ## Contributing
 Just open an issue or make a pull request. All contributions are welcome.
 

+ 30 - 0
src/apps/basic-wrapper/coloring.ts

@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { CustomElementProperty } from 'mol-model-props/common/custom-element-property';
+import { Model, ElementIndex } from 'mol-model/structure';
+import { Color } from 'mol-util/color';
+
+export const StripedResidues = CustomElementProperty.create<number>({
+    isStatic: true,
+    name: 'basic-wrapper-residue-striping',
+    display: 'Residue Stripes',
+    getData(model: Model) {
+        const map = new Map<ElementIndex, number>();
+        const residueIndex = model.atomicHierarchy.residueAtomSegments.index;
+        for (let i = 0, _i = model.atomicHierarchy.atoms._rowCount; i < _i; i++) {
+            map.set(i as ElementIndex, residueIndex[i] % 2);
+        }
+        return map;
+    },
+    coloring: {
+        getColor(e) { return e === 0 ? Color(0xff0000) : Color(0x0000ff) },
+        defaultColor: Color(0x777777)
+    },
+    format(e) {
+        return e === 0 ? 'Odd stripe' : 'Even stripe'
+    }
+})

+ 31 - 8
src/apps/basic-wrapper/index.html

@@ -35,19 +35,39 @@
             #controls > hr {
                 margin: 5px 0;
             }
+
+            #controls > input, #controls > select {
+                width: 100%;
+                display: block;
+            }
         </style>
         <link rel="stylesheet" type="text/css" href="app.css" />
         <script type="text/javascript" src="./index.js"></script>
     </head>
     <body>
         <div id='controls'>
-
+            <h3>Source</h3>
+            <input type='text' id='url' placeholder='url' />
+            <input type='text' id='assemblyId' placeholder='assembly id' />
+            <select id='format'>
+                <option value='cif' selected>CIF</option>
+                <option value='pdb'>PDB</option>
+            </select>
         </div>
         <div id="app"></div>
-        <script>            
+        <script>      
+            function $(id) { return document.getElementById(id); }
+        
             var pdbId = '1grm', assemblyId= '1';
             var url = 'https://www.ebi.ac.uk/pdbe/static/entry/' + pdbId + '_updated.cif';
             var format = 'cif';
+            
+            $('url').value = url;
+            $('url').onchange = function (e) { url = e.target.value; }
+            $('assemblyId').value = assemblyId;
+            $('assemblyId').onchange = function (e) { assemblyId = e.target.value; }
+            $('format').value = format;
+            $('format').onchange = function (e) { format = e.target.value; }
 
             // var url = 'https://www.ebi.ac.uk/pdbe/entry-files/pdb' + pdbId + '.ent';
             // var format = 'pdb';
@@ -58,9 +78,8 @@
             BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId });
             BasicMolStarWrapper.toggleSpin();
 
-            addHeader('Source');
             addControl('Load Asym Unit', () => BasicMolStarWrapper.load({ url: url, format: format }));
-            addControl('Load Assembly 1', () => BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId }));
+            addControl('Load Assembly', () => BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId }));
 
             addSeparator();
 
@@ -73,7 +92,7 @@
 
             // adjust this number to make the animation faster or slower
             // requires to "restart" the animation if changed
-            BasicMolStarWrapper.animate.modelIndex.maxFPS = 4;
+            BasicMolStarWrapper.animate.modelIndex.maxFPS = 30;
 
             addControl('Play To End', () => BasicMolStarWrapper.animate.modelIndex.onceForward());
             addControl('Play To Start', () => BasicMolStarWrapper.animate.modelIndex.onceBackward());
@@ -81,24 +100,28 @@
             addControl('Play Loop', () => BasicMolStarWrapper.animate.modelIndex.loop());
             addControl('Stop', () => BasicMolStarWrapper.animate.modelIndex.stop());
 
+            addHeader('Misc');
+
+            addControl('Apply Stripes', () => BasicMolStarWrapper.coloring.applyStripes());
+
             ////////////////////////////////////////////////////////
 
             function addControl(label, action) {
                 var btn = document.createElement('button');
                 btn.onclick = action;
                 btn.innerText = label;
-                document.getElementById('controls').appendChild(btn);
+                $('controls').appendChild(btn);
             }
 
             function addSeparator() {
                 var hr = document.createElement('hr');
-                document.getElementById('controls').appendChild(hr);
+                $('controls').appendChild(hr);
             }
 
             function addHeader(header) {
                 var h = document.createElement('h3');
                 h.innerText = header;
-                document.getElementById('controls').appendChild(h);
+                $('controls').appendChild(h);
             }
         </script>
     </body>

+ 23 - 1
src/apps/basic-wrapper/index.ts

@@ -11,9 +11,10 @@ import { PluginCommands } from 'mol-plugin/command';
 import { StateTransforms } from 'mol-plugin/state/transforms';
 import { StructureRepresentation3DHelpers } from 'mol-plugin/state/transforms/representation';
 import { Color } from 'mol-util/color';
-import { PluginStateObject as PSO } from 'mol-plugin/state/objects';
+import { PluginStateObject as PSO, PluginStateObject } from 'mol-plugin/state/objects';
 import { AnimateModelIndex } from 'mol-plugin/state/animation/built-in';
 import { StateBuilder } from 'mol-state';
+import { StripedResidues } from './coloring';
 require('mol-plugin/skin/light.scss')
 
 type SupportedFormats = 'cif' | 'pdb'
@@ -30,6 +31,10 @@ class BasicWrapper {
                 showControls: false
             }
         });
+
+        this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add(StripedResidues.Descriptor.name, StripedResidues.colorTheme!);
+        this.plugin.lociLabels.addProvider(StripedResidues.labelProvider);
+        this.plugin.customModelProperties.register(StripedResidues.propertyProvider);
     }
 
     private download(b: StateBuilder.To<PSO.Root>, url: string) {
@@ -43,6 +48,7 @@ class BasicWrapper {
 
         return parsed
             .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 })
+            .apply(StateTransforms.Model.CustomModelProperties, { properties: [StripedResidues.Descriptor.name] }, { ref: 'props', props: { isGhost: false } })
             .apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: 'asm' });
     }
 
@@ -110,6 +116,22 @@ class BasicWrapper {
             stop: () => this.plugin.state.animation.stop()
         }
     }
+
+    coloring = {
+        applyStripes: async () => {
+            const state = this.plugin.state.dataState;
+
+            const visuals = state.selectQ(q => q.ofType(PluginStateObject.Molecule.Representation3D).filter(c => c.transform.transformer === StateTransforms.Representation.StructureRepresentation3D));
+            const tree = state.build();
+            const colorTheme = { name: StripedResidues.Descriptor.name, params: this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.get(StripedResidues.Descriptor.name).defaultValues };
+
+            for (const v of visuals) {
+                tree.to(v.transform.ref).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
+            }
+
+            await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
+        }
+    }
 }
 
 (window as any).BasicMolStarWrapper = new BasicWrapper();

+ 128 - 0
src/mol-model-props/common/custom-element-property.ts

@@ -0,0 +1,128 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { ElementIndex, Model, ModelPropertyDescriptor } from 'mol-model/structure';
+import { StructureElement } from 'mol-model/structure/structure';
+import { Location } from 'mol-model/location';
+import { CustomPropertyRegistry } from './custom-property-registry';
+import { Task } from 'mol-task';
+import { ThemeDataContext, ThemeProvider } from 'mol-theme/theme';
+import { ColorTheme, LocationColor } from 'mol-theme/color';
+import { Color } from 'mol-util/color';
+import { TableLegend } from 'mol-util/color/tables';
+import { Loci } from 'mol-model/loci';
+import { OrderedSet } from 'mol-data/int';
+
+export { CustomElementProperty };
+
+namespace CustomElementProperty {
+    export interface CreateParams<T> {
+        isStatic: boolean,
+        name: string,
+        autoAttach?: boolean,
+        display: string,
+        attachableTo?: (model: Model) => boolean,
+        getData(model: Model): Map<ElementIndex, T> | Promise<Map<ElementIndex, T>>,
+        format?(e: T): string,
+        coloring?: {
+            getColor: (e: T) => Color,
+            defaultColor: Color
+        }
+    }
+
+    export function create<T>(params: CreateParams<T>) {
+        const name = params.name;
+
+        const Descriptor = ModelPropertyDescriptor({
+            isStatic: params.isStatic,
+            name: params.name,
+        });
+
+        function attach(model: Model) {
+            return Task.create(`Attach ${params.display}`, async () => {
+                const data = await params.getData(model);
+
+                if (params.isStatic) {
+                    model._staticPropertyData[name] = data;
+                } else {
+                    model._dynamicPropertyData[name] = data;
+                }
+
+                model.customProperties.add(Descriptor);
+
+                return true;
+            })
+        }
+
+        function getStatic(e: StructureElement) { return e.unit.model._staticPropertyData[name].get(e.element); }
+        function getDynamic(e: StructureElement) { return e.unit.model._staticPropertyData[name].get(e.element); }
+
+        const propertyProvider: CustomPropertyRegistry.Provider = {
+            option: [name, params.display],
+            descriptor: Descriptor,
+            defaultSelected: !!params.autoAttach,
+            attachableTo: params.attachableTo || (() => true),
+            attach
+        };
+
+        const get = params.isStatic ? getStatic : getDynamic;
+
+        function has(model: Model) { return model.customProperties.has(Descriptor); }
+
+        function Coloring(ctx: ThemeDataContext, props: {}): ColorTheme<{}> {
+            let color: LocationColor;
+            const getColor = params.coloring!.getColor;
+            const defaultColor = params.coloring!.defaultColor;
+
+            if (ctx.structure && !ctx.structure.isEmpty && has(ctx.structure.models[0])) {
+                color = (location: Location) => {
+                    if (StructureElement.isLocation(location)) {
+                        const e = get(location);
+                        if (typeof e !== 'undefined') return getColor(e);
+                    }
+                    return defaultColor;
+                }
+            } else {
+                color = () => defaultColor;
+            }
+
+            return {
+                factory: Coloring,
+                granularity: 'group',
+                color: color,
+                props: props,
+                description: 'Assign element colors based on the provided data.',
+                legend: TableLegend([])
+            };
+        }
+
+        const colorTheme: ThemeProvider<ColorTheme<{}>, {}> = {
+            label: params.display,
+            factory: Coloring,
+            getParams: () => ({}),
+            defaultValues: {},
+            isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && !ctx.structure.isEmpty && has(ctx.structure.models[0])
+        }
+
+        function LabelProvider(loci: Loci): string | undefined {
+            if (loci.kind === 'element-loci') {
+                const e = loci.elements[0];
+                if (!has(e.unit.model)) return void 0;
+                return params.format!(get(StructureElement.create(e.unit, e.unit.elements[OrderedSet.getAt(e.indices, 0)])));
+            }
+            return void 0;
+        }
+
+        return {
+            Descriptor,
+            attach,
+            get,
+            propertyProvider,
+            colorTheme: params.coloring ? colorTheme : void 0,
+            labelProvider: params.format ? LabelProvider : ((loci: Loci) => void 0)
+        };
+    }
+}

+ 0 - 0
src/mol-plugin/util/custom-prop-registry.ts → src/mol-model-props/common/custom-property-registry.ts


+ 18 - 18
src/mol-model/structure/model/properties/utils/secondary-structure.ts

@@ -48,7 +48,7 @@ export function computeModelDSSP(hierarchy: AtomicHierarchy, conformation: Atomi
     for (let i = 0, il = proteinResidues.length; i < il; ++i) {
         type[proteinResidues[i]] = assignment[i]
     }
-    
+
     const secondaryStructure: SecondaryStructure = {
         type,
         key: [], // TODO
@@ -294,7 +294,7 @@ function getDSSPAssignment(flags: Uint32Array, useOriginal = false) {
  * Q = -332 * 0.42 * 0.20
  *
  * f is the dimensional factor
- * 
+ *
  * q1 and q2 are partial charges which are placed on the C,O
  * (+q1,-q1) and N,H (-q2,+q2)
  */
@@ -313,7 +313,7 @@ function calcHbondEnergy(oPos: Vec3, cPos: Vec3, nPos: Vec3, hPos: Vec3) {
     const distON = Vec3.distance(oPos, nPos)
 
     const e1 = Q / distOH - Q / distCH
-	const e2 = Q / distCN - Q / distON
+    const e2 = Q / distCN - Q / distON
     return e1 + e2
 }
 
@@ -321,7 +321,7 @@ function calcHbondEnergy(oPos: Vec3, cPos: Vec3, nPos: Vec3, hPos: Vec3) {
  * The basic turn pattern is a single H bond of type (i, i + n).
  * We assign an n-turn at residue i if there is an H bond from CO(i) to NH(i + n),
  * i.e., “n-turn(i)=: Hbond(i, i + n), n = 3, 4, 5.”
- * 
+ *
  * Type: T
  */
 function assignTurns(ctx: DSSPContext) {
@@ -329,7 +329,7 @@ function assignTurns(ctx: DSSPContext) {
     const { chains, residueAtomSegments, chainAtomSegments } = hierarchy
     const { label_asym_id } = chains
 
-    const turnFlag = [ 0, 0, 0, DSSPType.Flag.T3, DSSPType.Flag.T4, DSSPType.Flag.T5 ]
+    const turnFlag = [0, 0, 0, DSSPType.Flag.T3, DSSPType.Flag.T4, DSSPType.Flag.T5]
 
     for (let i = 0, il = proteinResidues.length; i < il; ++i) {
         const rI = proteinResidues[i]
@@ -354,18 +354,18 @@ function assignTurns(ctx: DSSPContext) {
 
 /**
  * Two nonoverlapping stretches of three residues each, i - 1, i, i + 1 and j - 1, j, j + 1,
- * form either a parallel or antiparallel bridge, depending on which of 
+ * form either a parallel or antiparallel bridge, depending on which of
  * two basic patterns is matched. We assign a bridge between residues i and j
  * if there are two H bonds characteristic of P-structure; in particular,
- * 
+ *
  * Parallel Bridge(i, j) =:
  *      [Hbond(i - 1, j) and Hbond(j, i + 1)] or
  *      [Hbond(j - 1, i) and Hbond(i, j + 1)]
- * 
+ *
  * Antiparallel Bridge(i, j) =:
  *      [Hbond(i, j) and Hbond(j, i)] or
  *      [Hbond(i - 1, j + 1) and Hbond(j - 1, i + l)]
- * 
+ *
  * Type: B
  */
 function assignBridges(ctx: DSSPContext) {
@@ -378,7 +378,7 @@ function assignBridges(ctx: DSSPContext) {
         for (let t = offset[k], _t = offset[k + 1]; t < _t; t++) {
             const l = b[t]
             if (k > l) continue
-            
+
             // Parallel Bridge(i, j) =: [Hbond(i - 1, j) and Hbond(j, i + 1)]
             i = k + 1 // k is i - 1
             j = l
@@ -418,18 +418,18 @@ function assignBridges(ctx: DSSPContext) {
  * A minimal helix is defined by two consecutive n-turns.
  * For example, a 4-helix, of minimal length 4 from residues i to i + 3,
  * requires 4-turns at residues i - 1 and i,
- * 
+ *
  *      3-helix(i,i + 2)=: [3-turn(i - 1) and 3-turn(i)]
  *      4-helix(i,i + 3)=: [4-turn(i - 1) and 4-turn(i)]
  *      5-helix(i,i + 4)=: [5-turn(i - 1) and 5-turn(i)]
- * 
+ *
  * Type: G (n=3), H (n=4), I (n=5)
  */
 function assignHelices(ctx: DSSPContext) {
     const { proteinResidues, flags } = ctx
-    
-    const turnFlag = [ 0, 0, 0, DSSPType.Flag.T3, DSSPType.Flag.T4, DSSPType.Flag.T5 ]
-    const helixFlag = [ 0, 0, 0, DSSPType.Flag.G, DSSPType.Flag.H, DSSPType.Flag.I ]
+
+    const turnFlag = [0, 0, 0, DSSPType.Flag.T3, DSSPType.Flag.T4, DSSPType.Flag.T5]
+    const helixFlag = [0, 0, 0, DSSPType.Flag.G, DSSPType.Flag.H, DSSPType.Flag.I]
 
     for (let i = 1, il = proteinResidues.length; i < il; ++i) {
         const fI = DSSPType.create(flags[i])
@@ -447,7 +447,7 @@ function assignHelices(ctx: DSSPContext) {
 
 /**
  * ladder=: set of one or more consecutive bridges of identical type
- * 
+ *
  * Type: E
  */
 function assignLadders(ctx: DSSPContext) {
@@ -456,7 +456,7 @@ function assignLadders(ctx: DSSPContext) {
 
 /**
  * sheet=: set of one or more ladders connected by shared residues
- * 
+ *
  * Type: E
  */
 function assignSheets(ctx: DSSPContext) {
@@ -465,7 +465,7 @@ function assignSheets(ctx: DSSPContext) {
 
 /**
  * Bend(i) =: [angle ((CW - Ca(i - 2)),(C"(i + 2) - C"(i))) > 70"]
- * 
+ *
  * Type: S
  */
 function assignBends(ctx: DSSPContext) {

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

@@ -9,10 +9,10 @@ import { StructureQualityReport } from 'mol-model-props/pdbe/structure-quality-r
 import { StructureQualityReportColorTheme } from 'mol-model-props/pdbe/themes/structure-quality-report';
 import { Loci } from 'mol-model/loci';
 import { StructureElement } from 'mol-model/structure';
-import { CustomPropertyRegistry } from 'mol-plugin/util/custom-prop-registry';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { PluginBehavior } from '../../../behavior';
 import { ThemeDataContext } from 'mol-theme/theme';
+import { CustomPropertyRegistry } from 'mol-model-props/common/custom-property-registry';
 
 export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: boolean }>({
     name: 'pdbe-structure-quality-report-prop',

+ 1 - 1
src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts

@@ -7,12 +7,12 @@
 import { PluginBehavior } from 'mol-plugin/behavior';
 import { ParamDefinition as PD } from 'mol-util/param-definition'
 import { AssemblySymmetry } from 'mol-model-props/rcsb/assembly-symmetry';
-import { CustomPropertyRegistry } from 'mol-plugin/util/custom-prop-registry';
 import { AssemblySymmetryClusterColorThemeProvider } from 'mol-model-props/rcsb/themes/assembly-symmetry-cluster';
 import { AssemblySymmetryAxesRepresentationProvider } from 'mol-model-props/rcsb/representations/assembly-symmetry-axes';
 import { Loci, isDataLoci } from 'mol-model/loci';
 import { OrderedSet } from 'mol-data/int';
 import { Table } from 'mol-data/db';
+import { CustomPropertyRegistry } from 'mol-model-props/common/custom-property-registry';
 
 export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean }>({
     name: 'rcsb-assembly-symmetry-prop',

+ 1 - 1
src/mol-plugin/context.ts

@@ -24,7 +24,6 @@ import { TaskManager } from './util/task-manager';
 import { Color } from 'mol-util/color';
 import { LociLabelEntry, LociLabelManager } from './util/loci-label-manager';
 import { ajaxGet } from 'mol-util/data-source';
-import { CustomPropertyRegistry } from './util/custom-prop-registry';
 import { VolumeRepresentationRegistry } from 'mol-repr/volume/registry';
 import { PLUGIN_VERSION, PLUGIN_VERSION_DATE } from './version';
 import { PluginLayout } from './layout';
@@ -32,6 +31,7 @@ import { List } from 'immutable';
 import { StateTransformParameters } from './ui/state/common';
 import { DataFormatRegistry } from './state/actions/volume';
 import { PluginBehavior } from './behavior/behavior';
+import { CustomPropertyRegistry } from 'mol-model-props/common/custom-property-registry';
 
 export class PluginContext {
     private disposed = false;

+ 27 - 4
src/mol-state/state/builder.ts

@@ -9,6 +9,7 @@ import { TransientTree } from '../tree/transient';
 import { StateObject, StateObjectCell } from '../object';
 import { StateTransform } from '../transform';
 import { StateTransformer } from '../transformer';
+import { State } from 'mol-state/state';
 
 export { StateBuilder }
 
@@ -24,7 +25,7 @@ namespace StateBuilder {
         lastUpdate?: StateTransform.Ref
     }
 
-    interface State {
+    interface BuildState {
         tree: TransientTree,
         editInfo: EditInfo
     }
@@ -38,7 +39,7 @@ namespace StateBuilder {
     }
 
     export class Root implements StateBuilder {
-        private state: State;
+        private state: BuildState;
         get editInfo() { return this.state.editInfo; }
 
         to<A extends StateObject>(ref: StateTransform.Ref) { return new To<A>(this.state, ref, this); }
@@ -90,8 +91,30 @@ namespace StateBuilder {
             return new To(this.state, t.ref, this.root);
         }
 
+        /**
+         * Updates a transform in an instantiated tree, passing the transform's source into the providers
+         *
+         * This only works if the transform source is NOT updated by the builder. Use at own discression.
+         */
+        updateInState<T extends StateTransformer<any, A, any>>(transformer: T, state: State, provider: (old: StateTransformer.Params<T>, a: StateTransformer.From<T>) => StateTransformer.Params<T>): Root {
+            const old = this.state.tree.transforms.get(this.ref)!;
+            const cell = state.cells.get(this.ref);
+            if (!cell || !cell.sourceRef) throw new Error('Source cell is not present in the tree.');
+            const parent = state.cells.get(cell.sourceRef);
+            if (!parent || !parent.obj) throw new Error('Parent cell is not present or computed.');
+
+            const params = provider(old.params as any, parent.obj as any);
+
+            if (this.state.tree.setParams(this.ref, params)) {
+                this.editInfo.count++;
+                this.editInfo.lastUpdate = this.ref;
+            }
+
+            return this.root;
+        }
+
         update<T extends StateTransformer<any, A, any>>(transformer: T, params: (old: StateTransformer.Params<T>) => StateTransformer.Params<T>): Root
-        update(params: any): Root
+        update<T extends StateTransformer<any, A, any> = StateTransformer<any, A, any>>(params: StateTransformer.Params<T>): Root
         update<T extends StateTransformer<any, A, any>>(paramsOrTransformer: T, provider?: (old: StateTransformer.Params<T>) => StateTransformer.Params<T>) {
             let params: any;
             if (provider) {
@@ -115,7 +138,7 @@ namespace StateBuilder {
 
         getTree(): StateTree { return this.state.tree.asImmutable(); }
 
-        constructor(private state: State, ref: StateTransform.Ref, private root: Root) {
+        constructor(private state: BuildState, ref: StateTransform.Ref, private root: Root) {
             this.ref = ref;
             if (!this.state.tree.transforms.has(ref)) {
                 throw new Error(`Could not find node '${ref}'.`);

+ 16 - 0
src/mol-state/state/selection.ts

@@ -97,6 +97,14 @@ namespace StateSelection {
             });
         }
 
+        export function ofType(type: StateObject.Ctor) {
+            return build(() => state => {
+                const ctx = { ret: [] as StateObjectCell[], cells: state.cells, type: type.type };
+                StateTree.doPreOrder(state.tree, state.tree.root, ctx, _findOfType);
+                return ctx.ret;
+            });
+        }
+
         function _findRootsOfType(n: StateTransform, _: any, s: { type: StateObject.Type, roots: StateObjectCell[], cells: State.Cells }) {
             const cell = s.cells.get(n.ref);
             if (cell && cell.obj && cell.obj.type === s.type) {
@@ -105,6 +113,14 @@ namespace StateSelection {
             }
             return true;
         }
+
+        function _findOfType(n: StateTransform, _: any, s: { type: StateObject.Type, ret: StateObjectCell[], cells: State.Cells }) {
+            const cell = s.cells.get(n.ref);
+            if (cell && cell.obj && cell.obj.type === s.type) {
+                s.ret.push(cell);
+            }
+            return true;
+        }
     }
 
     registerModifier('flatMap', flatMap);

+ 1 - 1
src/perf-tests/cif-encoder.ts

@@ -60,7 +60,7 @@ function testBinary() {
     enc.writeCategory(getCat('cat2'), [{ rowCount: 1, fields: category2fields }]);
     enc.encode();
     const data = enc.getData() as Uint8Array;
-    fs.writeFileSync('e:/test/mol-star/test.bcif', new Buffer(data));
+    fs.writeFileSync('e:/test/mol-star/test.bcif', Buffer.from(data));
     console.log('written binary');
 }
 

+ 7 - 0
src/servers/model/preprocess/master.ts

@@ -26,6 +26,7 @@ cmdParser.addArgument(['--folderNumProcesses', '-fp'], { help: 'Convert folder n
 
 interface CmdArgs {
     // bulk?: string,
+    help?: any,
     cfg?: string,
     input?: string,
     outCIF?: string,
@@ -36,6 +37,7 @@ interface CmdArgs {
     folderNumProcesses?: string
 }
 
+
 export interface PreprocessConfig {
     numProcesses?: number,
     customProperties?: ModelPropertyProviderConfig | string
@@ -43,6 +45,11 @@ export interface PreprocessConfig {
 
 const cmdArgs = cmdParser.parseArgs() as CmdArgs;
 
+if (Object.keys(cmdArgs).filter(k => (cmdArgs as any)[k] !== null).length === 0 || typeof cmdArgs.help !== 'undefined') {
+    cmdParser.printHelp();
+    process.exit(0);
+}
+
 let entries: PreprocessEntry[] = []
 let config: PreprocessConfig = { numProcesses: 1, customProperties: void 0 }
 

+ 37 - 2
src/servers/model/preprocess/preprocess.ts

@@ -4,7 +4,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { readStructureWrapper, resolveStructures } from '../server/structure-wrapper';
+import { readStructureWrapper, resolveStructures, readDataAndFrame } from '../server/structure-wrapper';
 import { classifyCif } from './converter';
 import { Structure } from 'mol-model/structure';
 import { CifWriter } from 'mol-io/writer/cif';
@@ -15,7 +15,14 @@ import { ModelPropertiesProvider } from '../property-provider';
 
 // TODO: error handling
 
-export async function preprocessFile(filename: string, propertyProvider?: ModelPropertiesProvider, outputCif?: string, outputBcif?: string) {
+export function preprocessFile(filename: string, propertyProvider?: ModelPropertiesProvider, outputCif?: string, outputBcif?: string) {
+    return propertyProvider
+        ? preprocess(filename, propertyProvider, outputCif, outputBcif)
+        : convert(filename, outputCif, outputBcif);
+}
+
+
+async function preprocess(filename: string, propertyProvider?: ModelPropertiesProvider, outputCif?: string, outputBcif?: string) {
     const input = await readStructureWrapper('entry', '_local_', filename, propertyProvider);
     const categories = await classifyCif(input.cifFrame);
     const inputStructures = (await resolveStructures(input))!;
@@ -36,6 +43,34 @@ export async function preprocessFile(filename: string, propertyProvider?: ModelP
     }
 }
 
+async function convert(filename: string, outputCif?: string, outputBcif?: string) {
+    const { frame } = await readDataAndFrame(filename);
+    const categories = await classifyCif(frame);
+
+    if (outputCif) {
+        const writer = wrapFileToWriter(outputCif);
+        const encoder = CifWriter.createEncoder({ binary: false });
+        encodeConvert(frame.header, categories, encoder, writer);
+        writer.end();
+    }
+
+    if (outputBcif) {
+        const writer = wrapFileToWriter(outputBcif);
+        const encoder = CifWriter.createEncoder({ binary: true, binaryAutoClassifyEncoding: true });
+        encodeConvert(frame.header, categories, encoder, writer);
+        writer.end();
+    }
+}
+
+function encodeConvert(header: string, categories: CifWriter.Category[], encoder: CifWriter.Encoder, writer: Writer) {
+    encoder.startDataBlock(header);
+    for (const cat of categories) {
+        encoder.writeCategory(cat);
+    }
+    encoder.encode();
+    encoder.writeTo(writer);
+}
+
 function encode(structure: Structure, header: string, categories: CifWriter.Category[], encoder: CifWriter.Encoder, exportCtx: CifExportContext, writer: Writer) {
     const skipCategoryNames = new Set<string>(categories.map(c => c.name));
     encoder.startDataBlock(header);

+ 3 - 3
src/servers/model/property-provider.ts

@@ -18,11 +18,11 @@ export type AttachModelProperty = (args: { model: Model, params: any, cache: any
 export type AttachModelProperties = (args: { model: Model, params: any, cache: any }) => Promise<any>[]
 export type ModelPropertiesProvider = (model: Model, cache: any) => Promise<any>[]
 
-export function createModelPropertiesProviderFromConfig(): ModelPropertiesProvider {
+export function createModelPropertiesProviderFromConfig() {
     return createModelPropertiesProvider(Config.customProperties);
 }
 
-export function createModelPropertiesProvider(configOrPath: ModelPropertyProviderConfig | string | undefined): ModelPropertiesProvider {
+export function createModelPropertiesProvider(configOrPath: ModelPropertyProviderConfig | string | undefined): ModelPropertiesProvider | undefined {
     let config: ModelPropertyProviderConfig;
     if (typeof configOrPath === 'string') {
         try {
@@ -35,7 +35,7 @@ export function createModelPropertiesProvider(configOrPath: ModelPropertyProvide
         config = configOrPath!;
     }
 
-    if (!config || !config.sources || config.sources.length === 0) return () => [];
+    if (!config || !config.sources || config.sources.length === 0) return void 0;
 
     const ps: AttachModelProperties[] = [];
     for (const p of config.sources) {

+ 1 - 1
src/servers/model/server/api-local.ts

@@ -84,7 +84,7 @@ export function wrapFileToWriter(fn: string) {
         },
         writeBinary(this: any, data: Uint8Array) {
             this.open();
-            fs.writeSync(this.file, new Buffer(data.buffer));
+            fs.writeSync(this.file, Buffer.from(data.buffer));
             return true;
         },
         writeString(this: any, data: string) {

+ 1 - 1
src/servers/model/server/api-web.ts

@@ -39,7 +39,7 @@ function wrapResponse(fn: string, res: express.Response) {
         },
         writeBinary(this: any, data: Uint8Array) {
             if (!this.headerWritten) this.writeHeader(true);
-            return res.write(new Buffer(data.buffer));
+            return res.write(Buffer.from(data.buffer));
         },
         writeString(this: any, data: string) {
             if (!this.headerWritten) this.writeHeader(false);

+ 2 - 2
src/servers/model/server/query.ts

@@ -30,7 +30,7 @@ const perf = new PerformanceMonitor();
 let _propertyProvider: ModelPropertiesProvider;
 function propertyProvider() {
     if (_propertyProvider) return _propertyProvider;
-    _propertyProvider = createModelPropertiesProviderFromConfig();
+    _propertyProvider = createModelPropertiesProviderFromConfig() || (() => []);
     return _propertyProvider;
 }
 
@@ -156,7 +156,7 @@ const _model_server_stats_fields: CifField<number, Stats>[] = [
 
 const _model_server_result: CifWriter.Category<Job> = {
     name: 'model_server_result',
-    instance: (job) => CifWriter.categoryInstance(_model_server_result_fields,{ data: job, rowCount: 1 })
+    instance: (job) => CifWriter.categoryInstance(_model_server_result_fields, { data: job, rowCount: 1 })
 };
 
 const _model_server_error: CifWriter.Category<string> = {

+ 13 - 7
src/servers/model/server/structure-wrapper.ts

@@ -90,24 +90,30 @@ async function parseCif(data: string|Uint8Array) {
     return parsed.result;
 }
 
-export async function readStructureWrapper(key: string, sourceId: string | '_local_', entryId: string, propertyProvider: ModelPropertiesProvider | undefined) {
-    const filename = sourceId === '_local_' ? entryId : Config.mapFile(sourceId, entryId);
-    if (!filename) throw new Error(`Cound not map '${key}' to a valid filename.`);
-    if (!fs.existsSync(filename)) throw new Error(`Could not find source file for '${key}'.`);
-
+export async function readDataAndFrame(filename: string, key?: string) {
     perf.start('read');
     let data;
     try {
         data = await readFile(filename);
     } catch (e) {
-        ConsoleLogger.error(key, '' + e);
-        throw new Error(`Could not read the file for '${key}' from disk.`);
+        ConsoleLogger.error(key || filename, '' + e);
+        throw new Error(`Could not read the file for '${key || filename}' from disk.`);
     }
 
     perf.end('read');
     perf.start('parse');
     const frame = (await parseCif(data)).blocks[0];
     perf.end('parse');
+
+    return { data, frame };
+}
+
+export async function readStructureWrapper(key: string, sourceId: string | '_local_', entryId: string, propertyProvider: ModelPropertiesProvider | undefined) {
+    const filename = sourceId === '_local_' ? entryId : Config.mapFile(sourceId, entryId);
+    if (!filename) throw new Error(`Cound not map '${key}' to a valid filename.`);
+    if (!fs.existsSync(filename)) throw new Error(`Could not find source file for '${key}'.`);
+
+    const { data, frame } = await readDataAndFrame(filename, key);
     perf.start('createModel');
     const models = await trajectoryFromMmCIF(frame).run();
     perf.end('createModel');

+ 1 - 1
src/servers/model/test.ts

@@ -13,7 +13,7 @@ function wrapFile(fn: string) {
         },
         writeBinary(this: any, data: Uint8Array) {
             this.open();
-            fs.writeSync(this.file, new Buffer(data));
+            fs.writeSync(this.file, Buffer.from(data));
             return true;
         },
         writeString(this: any, data: string) {

+ 1 - 1
src/servers/volume/common/binary-schema.ts

@@ -97,7 +97,7 @@ function writeElement(e: Element, buffer: Buffer, src: any, offset: number) {
 
 function write(element: Element, src: any) {
     const size = byteCount(element, src);
-    const buffer = new Buffer(size);
+    const buffer = Buffer.alloc(size);
     writeElement(element, buffer, src, 0);
     return buffer;
 }

+ 1 - 1
src/servers/volume/common/file.ts

@@ -57,7 +57,7 @@ export function createFile(filename: string) {
     });
 }
 
-const smallBuffer = SimpleBuffer.fromBuffer(new Buffer(8));
+const smallBuffer = SimpleBuffer.fromBuffer(Buffer.alloc(8));
 export async function writeInt(file: FileHandle, value: number, position: number) {
     smallBuffer.writeInt32LE(value, 0);
     await file.writeBuffer(position, smallBuffer, 4);

+ 1 - 1
src/servers/volume/pack/main.ts

@@ -40,7 +40,7 @@ function updateAllocationProgress(progress: Data.Progress, progressDone: number)
  */
 async function allocateFile(ctx: Data.Context) {
     const { totalByteSize, file } = ctx;
-    const buffer = new Buffer(Math.min(totalByteSize, 8 * 1024 * 1024));
+    const buffer = Buffer.alloc(Math.min(totalByteSize, 8 * 1024 * 1024));
     const progress: Data.Progress = { current: 0, max: Math.ceil(totalByteSize / buffer.byteLength) };
     let written = 0;
     while (written < totalByteSize) {

+ 3 - 3
src/servers/volume/pack/sampling.ts

@@ -20,11 +20,11 @@ export async function createContext(filename: string, channels: Format.Context[]
     const { extent, valueType, grid, origin } = channels[0].data.header;
 
     const samplingCounts = getSamplingCounts(extent, blockSize);
-    const cubeBuffer = new Buffer(new ArrayBuffer(channels.length * blockSize * blockSize * blockSize * getElementByteSize(valueType)));
+    const cubeBuffer = Buffer.from(new ArrayBuffer(channels.length * blockSize * blockSize * blockSize * getElementByteSize(valueType)));
 
     const litteEndianCubeBuffer = SimpleBuffer.IsNativeEndianLittle
         ? cubeBuffer
-        : new Buffer(new ArrayBuffer(channels.length * blockSize * blockSize * blockSize * getElementByteSize(valueType)));
+        : Buffer.from(new ArrayBuffer(channels.length * blockSize * blockSize * blockSize * getElementByteSize(valueType)));
 
     // The data can be periodic iff the extent is the same as the grid and origin is 0.
     if (grid.some((v, i) => v !== extent[i]) || origin.some(v => v !== 0)) {
@@ -100,7 +100,7 @@ function createBlockBuffer(sampleCount: number[], blockSize: number, valueType:
     for (let i = 0; i < numChannels; i++) values[i] = createTypedArray(valueType, sampleCount[0] * sampleCount[1] * blockSize);
     return {
         values,
-        buffers: values.map(xs => new Buffer(xs.buffer)),
+        buffers: values.map(xs => Buffer.from(xs.buffer)),
         slicesWritten: 0
     };
 }

+ 2 - 2
src/servers/volume/server/local-api.ts

@@ -28,7 +28,7 @@ export interface JobEntry {
     params: {
         /** Determines the detail level as specified in server-config */
         detail?: number,
-        /** 
+        /**
          * Determines the sampling level:
          * 1: Original data
          * 2: Downsampled by factor 1/2
@@ -121,7 +121,7 @@ function wrapFile(fn: string) {
         },
         writeBinary(this: any, data: Uint8Array) {
             this.open();
-            fs.writeSync(this.file, new Buffer(data));
+            fs.writeSync(this.file, Buffer.from(data));
             return true;
         },
         writeString(this: any, data: string) {

+ 1 - 1
src/servers/volume/server/web-api.ts

@@ -60,7 +60,7 @@ function wrapResponse(fn: string, res: express.Response) {
         },
         writeBinary(this: any, data: Uint8Array) {
             if (!this.headerWritten) this.writeHeader(true);
-            return res.write(new Buffer(data.buffer));
+            return res.write(Buffer.from(data.buffer));
         },
         writeString(this: any, data: string) {
             if (!this.headerWritten) this.writeHeader(false);

+ 1 - 0
webpack.config.js

@@ -88,6 +88,7 @@ module.exports = [
     createApp('viewer'),
     createApp('basic-wrapper'),
     createNodeApp('state-docs'),
+    createNodeEntryPoint('preprocess', 'servers/model', 'model-server'),
     createApp('model-server-query'),
 
     createBrowserTest('font-atlas'),