Browse Source

mol-plugin: user selections

David Sehnal 6 years ago
parent
commit
d1b8b6daea

+ 2 - 0
src/apps/state-docs/pd-to-md.ts

@@ -28,6 +28,8 @@ function paramInfo(param: PD.Any, offset: number): string {
         case 'group': return `Object with:\n${getParams(param.params, offset + 2)}`;
         case 'mapped': return `Object { name: string, params: object } where name+params are:\n${getMapped(param, offset + 2)}`;
         case 'line-graph': return `A list of 2d vectors [xi, yi][]`;
+        // TODO: support more languages
+        case 'script-expression': return `An expression in the specified language { language: 'mol-script', expressiong: string }`;
         default:
             const _: never = param;
             console.warn(`${_} has no associated UI component`);

+ 1 - 0
src/mol-plugin/index.ts

@@ -40,6 +40,7 @@ export const DefaultPluginSpec: PluginSpec = {
         PluginSpec.Action(StateTransforms.Model.StructureSymmetryFromModel),
         PluginSpec.Action(StateTransforms.Model.StructureFromModel),
         PluginSpec.Action(StateTransforms.Model.ModelFromTrajectory),
+        PluginSpec.Action(StateTransforms.Model.UserStructureSelection),
         PluginSpec.Action(StateTransforms.Volume.VolumeFromCcp4),
         PluginSpec.Action(StateTransforms.Representation.StructureRepresentation3D),
         PluginSpec.Action(StateTransforms.Representation.StructureLabels3D),

+ 29 - 0
src/mol-plugin/state/transforms/model.ts

@@ -22,6 +22,8 @@ import { stringToWords } from 'mol-util/string';
 import { PluginStateObject as SO, PluginStateTransform } from '../objects';
 import { trajectoryFromGRO } from 'mol-model-formats/structure/gro';
 import { parseGRO } from 'mol-io/reader/gro/parser';
+import { parseMolScript } from 'mol-script/language/parser';
+import { transpileMolScript } from 'mol-script/script/mol-script/symbols';
 
 export { TrajectoryFromMmCif };
 export { TrajectoryFromPDB };
@@ -31,8 +33,10 @@ export { StructureFromModel };
 export { StructureAssemblyFromModel };
 export { StructureSymmetryFromModel };
 export { StructureSelection };
+export { UserStructureSelection };
 export { StructureComplexElement };
 export { CustomModelProperties };
+
 type TrajectoryFromMmCif = typeof TrajectoryFromMmCif
 const TrajectoryFromMmCif = PluginStateTransform.BuiltIn({
     name: 'trajectory-from-mmcif',
@@ -243,6 +247,31 @@ const StructureSelection = PluginStateTransform.BuiltIn({
     }
 });
 
+type UserStructureSelection = typeof UserStructureSelection
+const UserStructureSelection = PluginStateTransform.BuiltIn({
+    name: 'user-structure-selection',
+    display: { name: 'Structure Selection', description: 'Create a molecular structure from the specified query expression.' },
+    from: SO.Molecule.Structure,
+    to: SO.Molecule.Structure,
+    params: {
+        query: PD.ScriptExpression({ language: 'mol-script', expression: '(sel.atom.atom-groups :residue-test (= atom.resname ALA))' }),
+        label: PD.makeOptional(PD.Text(''))
+    }
+})({
+    apply({ a, params }) {
+        // TODO: use cache, add "update"
+        const parsed = parseMolScript(params.query.expression);
+        if (parsed.length === 0) throw new Error('No query');
+        const query = transpileMolScript(parsed[0]);
+        const compiled = compile<Sel>(query);
+        const result = compiled(new QueryContext(a.data));
+        const s = Sel.unionStructure(result);
+        if (s.elementCount === 0) return StateObject.Null;
+        const props = { label: `${params.label || 'Selection'}`, description: structureDesc(s) };
+        return new SO.Molecule.Structure(s, props);
+    }
+});
+
 namespace StructureComplexElement {
     export type Types = 'atomic-sequence' | 'water' | 'atomic-het' | 'spheres'
 }

+ 31 - 1
src/mol-plugin/ui/controls/parameters.tsx

@@ -63,6 +63,7 @@ function controlFor(param: PD.Any): ParamControl | undefined {
         case 'group': return GroupControl;
         case 'mapped': return MappedControl;
         case 'line-graph': return LineGraphControl;
+        case 'script-expression': return ScriptExpressionControl;
         default:
             const _: never = param;
             console.warn(`${_} has no associated UI component`);
@@ -77,7 +78,7 @@ export interface ParamProps<P extends PD.Base<any> = PD.Base<any>> { name: strin
 export type ParamControl = React.ComponentClass<ParamProps<any>>
 
 export abstract class SimpleParam<P extends PD.Any> extends React.PureComponent<ParamProps<P>> {
-    protected update(value: any) {
+    protected update(value: P['defaultValue']) {
         this.props.onChange({ param: this.props.param, name: this.props.name, value });
     }
 
@@ -562,3 +563,32 @@ export class ConvertedControl extends React.PureComponent<ParamProps<PD.Converte
         return <Converted param={this.props.param.converted} value={value} name={this.props.name} onChange={this.onChange} onEnter={this.props.onEnter} isDisabled={this.props.isDisabled} />
     }
 }
+
+export class ScriptExpressionControl extends SimpleParam<PD.ScriptExpression> {
+    onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+        const value = e.target.value;
+        if (value !== this.props.value.expression) {
+            this.update({ language: this.props.value.language, expression: value });
+        }
+    }
+
+    onKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
+        if (!this.props.onEnter) return;
+        if ((e.keyCode === 13 || e.charCode === 13)) {
+            this.props.onEnter();
+        }
+    }
+
+    renderControl() {
+        // TODO: improve!
+
+        const placeholder = this.props.param.label || camelCaseToWords(this.props.name);
+        return <input type='text'
+            value={this.props.value.expression || ''}
+            placeholder={placeholder}
+            onChange={this.onChange}
+            onKeyPress={this.props.onEnter ? this.onKeyPress : void 0}
+            disabled={this.props.isDisabled}
+        />;
+    }
+}

+ 11 - 1
src/mol-util/param-definition.ts

@@ -213,7 +213,14 @@ export namespace ParamDefinition {
         return { type: 'conditioned', select: Select<string>(conditionForValue(defaultValue) as string, options), defaultValue, conditionParams, conditionForValue, conditionedValue };
     }
 
-    export type Any = Value<any> | Select<any> | MultiSelect<any> | Boolean | Text | Color | Vec3 | Numeric | FileParam | Interval | LineGraph | ColorScale<any> | Group<any> | Mapped<any> | Converted<any, any> | Conditioned<any, any, any>
+    export interface ScriptExpression extends Base<{ language: 'mol-script', expression: string }> {
+        type: 'script-expression'
+    }
+    export function ScriptExpression(defaultValue: ScriptExpression['defaultValue'], info?: Info): ScriptExpression {
+        return setInfo<ScriptExpression>({ type: 'script-expression', defaultValue }, info)
+    }
+
+    export type Any = Value<any> | Select<any> | MultiSelect<any> | Boolean | Text | Color | Vec3 | Numeric | FileParam | Interval | LineGraph | ColorScale<any> | Group<any> | Mapped<any> | Converted<any, any> | Conditioned<any, any, any> | ScriptExpression
 
     export type Params = { [k: string]: Any }
     export type Values<T extends Params> = { [k in keyof T]: T[k]['defaultValue'] }
@@ -301,6 +308,9 @@ export namespace ParamDefinition {
             return true;
         } else if (p.type === 'vec3') {
             return Vec3Data.equals(a, b);
+        } else if (p.type === 'script-expression') {
+            const u = a as ScriptExpression['defaultValue'], v = b as ScriptExpression['defaultValue'];
+            return u.language === v.language && u.expression === v.expression;
         } else if (typeof a === 'object' && typeof b === 'object') {
             return shallowEqual(a, b);
         }