Browse Source

mol-model: cif export copyAllCategories option
- support in model-server
- moved servers build config
- fixed swagger template not working with the separate server build

David Sehnal 5 years ago
parent
commit
05c35a3a3a

+ 2 - 2
package.json

@@ -16,7 +16,7 @@
     "test": "npm run lint && jest",
     "build": "npm run build-tsc && npm run build-extra && npm run build-webpack",
     "build-viewer": "npm run build-tsc && npm run build-extra && npm run build-webpack-viewer",
-    "build-tsc": "tsc --incremental && tsc --build src/servers --incremental",
+    "build-tsc": "tsc --incremental && tsc --build tsconfig.servers.json --incremental",
     "build-extra": "cpx \"src/**/*.{scss,html,ico}\" lib/",
     "build-webpack": "webpack --mode production --config ./webpack.config.production.js",
     "build-webpack-viewer": "webpack --mode production --config ./webpack.config.viewer.js",
@@ -24,7 +24,7 @@
     "watch-viewer": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer\"",
     "watch-viewer-debug": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer-debug\"",
     "watch-tsc": "tsc --watch --incremental",
-    "watch-servers": "tsc --build src/servers --watch --incremental",
+    "watch-servers": "tsc --build tsconfig.servers.json --watch --incremental",
     "watch-extra": "cpx \"src/**/*.{scss,html,ico}\" lib/ --watch",
     "watch-webpack": "webpack -w --mode development --display minimal",
     "watch-webpack-viewer": "webpack -w --mode development --display errors-only --info-verbosity verbose --config ./webpack.config.viewer.js",

+ 7 - 0
src/mol-io/writer/cif/encoder.ts

@@ -108,6 +108,11 @@ export namespace Field {
             return this;
         }
 
+        add(field: Field<K, D>) {
+            this.fields.push(field);
+            return this;
+        }
+
         getFields() { return this.fields; }
     }
 
@@ -222,6 +227,8 @@ export namespace Category {
 }
 
 export interface Encoder<T = string | Uint8Array> extends EncoderBase {
+    readonly isBinary: boolean,
+
     setFilter(filter?: Category.Filter): void,
     isCategoryIncluded(name: string): boolean,
     setFormatter(formatter?: Category.Formatter): void,

+ 2 - 0
src/mol-io/writer/cif/encoder/binary.ts

@@ -28,6 +28,8 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
     private filter: Category.Filter = Category.DefaultFilter;
     private formatter: Category.Formatter = Category.DefaultFormatter;
 
+    readonly isBinary = true;
+
     binaryEncodingProvider: BinaryEncodingProvider | undefined = void 0;
 
     setFilter(filter?: Category.Filter) {

+ 2 - 0
src/mol-io/writer/cif/encoder/text.ts

@@ -19,6 +19,8 @@ export default class TextEncoder implements Encoder<string> {
     private filter: Category.Filter = Category.DefaultFilter;
     private formatter: Category.Formatter = Category.DefaultFormatter;
 
+    readonly isBinary = false;
+
     binaryEncodingProvider = void 0;
 
     setFilter(filter?: Category.Filter) {

+ 6 - 3
src/mol-model/structure/export/categories/atom_site_operator_mapping.ts

@@ -6,18 +6,21 @@
 
 import { SymmetryOperator } from '../../../../mol-math/geometry';
 import { CifExportContext } from '../mmcif';
-import { StructureElement, StructureProperties as P } from '../../structure';
+import { StructureElement, StructureProperties as P, CifExportCategoryInfo } from '../../structure';
 import Unit from '../../structure/unit';
 import { Segmentation } from '../../../../mol-data/int';
 import { CifWriter } from '../../../../mol-io/writer/cif';
 import { Column } from '../../../../mol-data/db';
 
-export function atom_site_operator_mapping(encoder: CifWriter.Encoder, ctx: CifExportContext) {
+
+export function atom_site_operator_mapping(ctx: CifExportContext): CifExportCategoryInfo | undefined {
     const entries = getEntries(ctx);
     if (entries.length === 0) return;
-    encoder.writeCategory(Category, entries, { ignoreFilter: true });
+    return [Category, entries, { ignoreFilter: true }];
 }
 
+export const AtomSiteOperatorMappingCategoryName = 'molstar_atom_site_operator_mapping';
+
 export const AtomSiteOperatorMappingSchema = {
     molstar_atom_site_operator_mapping: {
         label_asym_id: Column.Schema.Str(),

+ 38 - 0
src/mol-model/structure/export/categories/utils.ts

@@ -14,6 +14,7 @@ import { sortArray } from '../../../../mol-data/util';
 import { CifWriter } from '../../../../mol-io/writer/cif';
 import { CifExportContext } from '../mmcif';
 import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
+import { CifCategory, CifField, getCifFieldType } from '../../../../mol-io/reader/cif';
 
 export function getModelMmCifCategory<K extends keyof mmCIF_Schema>(model: Model, name: K): mmCIF_Database[K] | undefined {
     if (!MmcifFormat.is(model.sourceData)) return;
@@ -58,4 +59,41 @@ export function copy_mmCif_category(name: keyof mmCIF_Schema, condition?: (struc
             return CifWriter.Category.ofTable(table);
         }
     };
+}
+
+export function copy_source_mmCifCategory(encoder: CifWriter.Encoder, ctx: CifExportContext, category: CifCategory): CifWriter.Category<CifExportContext> | undefined {
+    if (!MmcifFormat.is(ctx.firstModel.sourceData)) return;
+
+    const fs = CifWriter.fields<number, undefined>();
+    if (encoder.isBinary) {
+        for (const f of category.fieldNames) {
+            // TODO: this could be optimized
+            const field = classifyField(f, category.getField(f)!);
+            fs.add(field);
+        }
+    } else {
+        for (const f of category.fieldNames) {
+            const field = category.getField(f)!;
+            fs.str(f, row => field.str(row));
+        }
+    }
+
+    const fields = fs.getFields();
+    return {
+        name: category.name,
+        instance() {
+            return { fields, source: [{ data: void 0, rowCount: category.rowCount }] };
+        }
+    };
+}
+
+function classifyField(name: string, field: CifField): CifWriter.Field {
+    const type = getCifFieldType(field);
+    if (type['@type'] === 'str') {
+        return { name, type: CifWriter.Field.Type.Str, value: field.str, valueKind: field.valueKind };
+    } else if (type['@type'] === 'float') {
+        return CifWriter.Field.float(name, field.float, { valueKind: field.valueKind, typedArray: Float64Array });
+    } else {
+        return CifWriter.Field.int(name, field.int, { valueKind: field.valueKind, typedArray: Int32Array });
+    }
 }

+ 93 - 16
src/mol-model/structure/export/mmcif.ts

@@ -13,10 +13,11 @@ import CifCategory = CifWriter.Category
 import { _struct_conf, _struct_sheet_range } from './categories/secondary-structure';
 import { _chem_comp, _pdbx_chem_comp_identifier, _pdbx_nonpoly_scheme } from './categories/misc';
 import { Model } from '../model';
-import { getUniqueEntityIndicesFromStructures, copy_mmCif_category } from './categories/utils';
+import { getUniqueEntityIndicesFromStructures, copy_mmCif_category, copy_source_mmCifCategory } from './categories/utils';
 import { _struct_asym, _entity_poly, _entity_poly_seq } from './categories/sequence';
 import { CustomPropertyDescriptor } from '../common/custom-property';
 import { atom_site_operator_mapping } from './categories/atom_site_operator_mapping';
+import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
 
 export interface CifExportContext {
     structures: Structure[],
@@ -24,6 +25,10 @@ export interface CifExportContext {
     cache: any
 }
 
+export type CifExportCategoryInfo =
+    | [CifWriter.Category, any /** context */, CifWriter.Encoder.WriteCategoryOptions]
+    | [CifWriter.Category, any /** context */]
+
 export namespace CifExportContext {
     export function create(structures: Structure | Structure[]): CifExportContext {
         const structureArray = Array.isArray(structures) ? structures : [structures];
@@ -99,8 +104,8 @@ export const mmCIF_Export_Filters = {
     }
 };
 
-function encodeCustomProp(customProp: CustomPropertyDescriptor, ctx: CifExportContext, encoder: CifWriter.Encoder, params: encode_mmCIF_categories_Params) {
-    if (!customProp.cifExport || customProp.cifExport.categories.length === 0) return;
+function getCustomPropCategories(customProp: CustomPropertyDescriptor, ctx: CifExportContext, params?: encode_mmCIF_categories_Params): CifExportCategoryInfo[] {
+    if (!customProp.cifExport || customProp.cifExport.categories.length === 0) return [];
 
     const prefix = customProp.cifExport.prefix;
     const cats = customProp.cifExport.categories;
@@ -114,14 +119,21 @@ function encodeCustomProp(customProp: CustomPropertyDescriptor, ctx: CifExportCo
             ctx.cache[propId + '__ctx'] = propCtx;
         }
     }
+
+    const ret: CifExportCategoryInfo[] = [];
     for (const cat of cats) {
-        if (params.skipCategoryNames && params.skipCategoryNames.has(cat.name)) continue;
+        if (params?.skipCategoryNames?.has(cat.name)) continue;
         if (cat.name.indexOf(prefix) !== 0) throw new Error(`Custom category '${cat.name}' name must start with prefix '${prefix}.'`);
-        encoder.writeCategory(cat, propCtx);
+        ret.push([cat, propCtx]);
     }
+    return ret;
 }
 
-type encode_mmCIF_categories_Params = { skipCategoryNames?: Set<string>, exportCtx?: CifExportContext }
+type encode_mmCIF_categories_Params = {
+    skipCategoryNames?: Set<string>,
+    exportCtx?: CifExportContext,
+    copyAllCategories?: boolean
+}
 
 /** Doesn't start a data block */
 export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structures: Structure | Structure[], params?: encode_mmCIF_categories_Params) {
@@ -129,30 +141,95 @@ export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structures:
     const models = first.models;
     if (models.length !== 1) throw 'Can\'t export stucture composed from multiple models.';
 
-    const _params = params || { };
-    const ctx: CifExportContext = params && params.exportCtx ? params.exportCtx : CifExportContext.create(structures);
+    const ctx: CifExportContext = params?.exportCtx || CifExportContext.create(structures);
 
+    if (params?.copyAllCategories && MmcifFormat.is(models[0].sourceData)) {
+        encode_mmCIF_categories_copyAll(encoder, ctx);
+    } else {
+        console.log('default');
+        encode_mmCIF_categories_default(encoder, ctx, params);
+    }
+}
+
+function encode_mmCIF_categories_default(encoder: CifWriter.Encoder, ctx: CifExportContext, params?: encode_mmCIF_categories_Params) {
     for (const cat of Categories) {
-        if (_params.skipCategoryNames && _params.skipCategoryNames.has(cat.name)) continue;
+        if (params?.skipCategoryNames && params?.skipCategoryNames.has(cat.name)) continue;
         encoder.writeCategory(cat, ctx);
     }
 
-    if ((!_params.skipCategoryNames || !_params.skipCategoryNames.has('atom_site')) && encoder.isCategoryIncluded('atom_site')) {
-        atom_site_operator_mapping(encoder, ctx);
+    if (!params?.skipCategoryNames?.has('atom_site') && encoder.isCategoryIncluded('atom_site')) {
+        const info = atom_site_operator_mapping(ctx);
+        if (info) encoder.writeCategory(info[0], info[1], info[2]);
     }
 
-    for (const customProp of models[0].customProperties.all) {
-        encodeCustomProp(customProp, ctx, encoder, _params);
+    const _params = params || { };
+    for (const customProp of ctx.firstModel.customProperties.all) {
+        for (const [cat, propCtx] of getCustomPropCategories(customProp, ctx, _params)) {
+            encoder.writeCategory(cat, propCtx);
+        }
     }
 
-    const structureCustomProps = new Set<CustomPropertyDescriptor>();
     for (const s of ctx.structures) {
         if (!s.hasCustomProperties) continue;
-        for (const p of s.customPropertyDescriptors.all) structureCustomProps.add(p);
+        for (const customProp of s.customPropertyDescriptors.all) {
+            for (const [cat, propCtx] of getCustomPropCategories(customProp, ctx, _params)) {
+                encoder.writeCategory(cat, propCtx);
+            }
+        }
     }
-    structureCustomProps.forEach(customProp => encodeCustomProp(customProp, ctx, encoder, _params));
 }
 
+function encode_mmCIF_categories_copyAll(encoder: CifWriter.Encoder, ctx: CifExportContext) {
+    const providedCategories = new Map<string, CifExportCategoryInfo>();
+
+    for (const cat of Categories) {
+        providedCategories.set(cat.name, [cat, ctx]);
+    }
+
+    const mapping = atom_site_operator_mapping(ctx);
+    if (mapping) providedCategories.set(mapping[0].name, mapping);
+
+    for (const customProp of ctx.firstModel.customProperties.all) {
+        for (const info of getCustomPropCategories(customProp, ctx)) {
+            providedCategories.set(info[0].name, info);
+        }
+    }
+
+    for (const s of ctx.structures) {
+        if (!s.hasCustomProperties) continue;
+        for (const customProp of s.customPropertyDescriptors.all) {
+            for (const info of getCustomPropCategories(customProp, ctx)) {
+                providedCategories.set(info[0].name, info);
+            }
+        }
+    }
+
+    const handled = new Set<string>();
+
+    const data = (ctx.firstModel.sourceData as MmcifFormat).data;
+    for (const catName of data.frame.categoryNames) {
+        handled.add(catName);
+
+        if (providedCategories.has(catName)) {
+            const info = providedCategories.get(catName)!;
+            encoder.writeCategory(info[0], info[1], info[2]);
+        } else {
+            if ((data.db as any)[catName]) {
+                const cat = copy_mmCif_category(catName as any);
+                encoder.writeCategory(cat, ctx);
+            } else {
+                const cat = copy_source_mmCifCategory(encoder, ctx, data.frame.categories[catName]);
+                if (cat) encoder.writeCategory(cat);
+            }
+        }
+    }
+
+    providedCategories.forEach((info, name) => {
+        if (!handled.has(name)) encoder.writeCategory(info[0], info[1], info[2]);
+    });
+}
+
+
 function to_mmCIF(name: string, structure: Structure, asBinary = false) {
     const enc = CifWriter.createEncoder({ binary: asBinary });
     enc.startDataBlock(name);

+ 2 - 3
src/servers/common/swagger-ui/index.ts

@@ -5,11 +5,11 @@
  */
 
 import * as express from 'express';
-import * as fs from 'fs';
 import { getAbsoluteFSPath } from 'swagger-ui-dist';
 import { ServeStaticOptions } from 'serve-static';
 import { interpolate } from '../../../mol-util/string';
 import { Handler } from 'express-serve-static-core';
+import IndexTemplate from './indexTemplate';
 
 export function swaggerUiAssetsHandler(options?: ServeStaticOptions): Handler {
     const opts = options || {};
@@ -25,8 +25,7 @@ export interface SwaggerUIOptions {
 }
 
 function createHTML(options: SwaggerUIOptions) {
-    const htmlTemplate = fs.readFileSync(`${__dirname}/indexTemplate.html`).toString();
-    return interpolate(htmlTemplate, options);
+    return interpolate(IndexTemplate, options);
 }
 
 export function swaggerUiIndexHandler(options: SwaggerUIOptions): express.Handler {

+ 8 - 8
src/servers/common/swagger-ui/indexTemplate.html → src/servers/common/swagger-ui/indexTemplate.ts

@@ -1,10 +1,10 @@
-<!DOCTYPE html>
+export default `<!DOCTYPE html>
 <html lang="en">
     <head>
         <meta charset="UTF-8">
-        <title>${title}</title>
-        <link rel="stylesheet" type="text/css" href="${apiPrefix}/swagger-ui.css" >
-        ${shortcutIconLink}
+        <title>\${title}</title>
+        <link rel="stylesheet" type="text/css" href="\${apiPrefix}/swagger-ui.css" >
+        \${shortcutIconLink}
 
         <style>
             html
@@ -30,8 +30,8 @@
     <body>
         <div id="swagger-ui"></div>
 
-        <script src="${apiPrefix}/swagger-ui-bundle.js"> </script>
-        <script src="${apiPrefix}/swagger-ui-standalone-preset.js"> </script>
+        <script src="\${apiPrefix}/swagger-ui-bundle.js"> </script>
+        <script src="\${apiPrefix}/swagger-ui-standalone-preset.js"> </script>
         <script>
             function HidePlugin() {
                 // this plugin overrides some components to return nothing
@@ -44,7 +44,7 @@
             }
             window.onload = function () {
                 var ui = SwaggerUIBundle({
-                    url: '${openapiJsonUrl}',
+                    url: '\${openapiJsonUrl}',
                     validatorUrl: null,
                     docExpansion: 'list',
                     dom_id: '#swagger-ui',
@@ -63,4 +63,4 @@
             }
         </script>
     </body>
-</html>
+</html>`;

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

@@ -13,11 +13,11 @@ import { Job, JobEntry, JobManager } from './jobs';
 import { resolveJob } from './query';
 import { StructureCache } from './structure-wrapper';
 
-
 export type Entry<Q extends QueryName = QueryName> = {
     input: string,
     query: Q,
     modelNums?: number[],
+    copyAllCategories?: boolean,
     params?: QueryParams<Q>,
 }
 
@@ -43,6 +43,7 @@ export async function runLocal(input: LocalInput) {
                 queryName: q.query,
                 queryParams: q.params || { },
                 modelNums: q.modelNums,
+                copyAllCategories: !!q.copyAllCategories
             })),
             writer: job.asTarGz
                 ? new TarballFileResultWriter(job.output, job.gzipLevel)

+ 7 - 2
src/servers/model/server/api-schema.ts

@@ -52,7 +52,7 @@ function getPaths() {
     const queryManyExample: MultipleQuerySpec = {
         queries: [
             { entryId: '1cbs', query: 'residueInteraction', params: { atom_site: [{ label_comp_id: 'REA' }], radius: 5 } },
-            { entryId: '1tqn', query: 'full' }
+            { entryId: '1tqn', query: 'full', copy_all_categories: true }
         ],
         encoding: 'cif',
         asTarGz: false
@@ -174,7 +174,12 @@ function getParamInfo(info: QueryParamInfo) {
         description: info.description,
         required: !!info.required,
         schema: {
-            type: info.type === QueryParamType.String ? 'string' : info.type === QueryParamType.Integer ? 'integer' : 'number',
+            type: info.type === QueryParamType.String
+                ? 'string' : info.type === QueryParamType.Integer
+                    ? 'integer'
+                    : info.type === QueryParamType.Boolean
+                        ? 'boolean'
+                        : 'number',
             enum: info.supportedValues ? info.supportedValues : void 0,
             default: info.defaultValue
         },

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

@@ -11,7 +11,8 @@ export interface MultipleQueryEntry<Name extends QueryName = QueryName> {
     entryId: string,
     query: Name,
     params?: QueryParams<Name>,
-    model_nums?: number[]
+    model_nums?: number[],
+    copy_all_categories?: boolean
 }
 
 export interface MultipleQuerySpec {

+ 4 - 2
src/servers/model/server/api-web.ts

@@ -63,7 +63,8 @@ function mapQuery(app: express.Express, queryName: string, queryDefinition: Quer
                 entryId,
                 queryName: queryName as any,
                 queryParams,
-                modelNums: commonParams.model_nums
+                modelNums: commonParams.model_nums,
+                copyAllCategories: !!commonParams.copy_all_categories
             })],
             writer: createResultWriter(res, commonParams.encoding === 'bcif', entryId, queryName),
             options: { binary: commonParams.encoding === 'bcif' }
@@ -134,7 +135,8 @@ function createMultiJob(spec: MultipleQuerySpec, res: express.Response) {
             entryId: q.entryId,
             queryName: q.query,
             queryParams: q.params || { },
-            modelNums: q.model_nums
+            modelNums: q.model_nums,
+            copyAllCategories: !!q.copy_all_categories
         })),
         writer,
         options: { binary: spec.encoding?.toLowerCase() === 'bcif', tarball: spec.asTarGz }

+ 5 - 0
src/servers/model/server/api.ts

@@ -13,6 +13,7 @@ export enum QueryParamType {
     JSON,
     String,
     Integer,
+    Boolean,
     Float
 }
 
@@ -44,12 +45,14 @@ export interface QueryDefinition<Params = any> {
 export const CommonQueryParamsInfo: QueryParamInfo[] = [
     { name: 'model_nums', type: QueryParamType.String, description: `A comma-separated list of model ids (i.e. 1,2). If set, only include atoms with the corresponding '_atom_site.pdbx_PDB_model_num' field.` },
     { name: 'encoding', type: QueryParamType.String, defaultValue: 'cif', description: `Determines the output encoding (text based 'CIF' or binary 'BCIF').`, supportedValues: ['cif', 'bcif'] },
+    { name: 'copy_all_categories', type: QueryParamType.Boolean, defaultValue: false, description: 'If true, copy all categories from the input file.' },
     { name: 'data_source', type: QueryParamType.String, defaultValue: '', description: 'Allows to control how the provided data source ID maps to input file (as specified by the server instance config).' }
 ];
 
 export interface CommonQueryParamsInfo {
     model_nums?: number[],
     encoding?: 'cif' | 'bcif',
+    copy_all_categories?: boolean
     data_source?: string
 }
 
@@ -234,6 +237,7 @@ function _normalizeQueryParams(params: { [p: string]: string }, paramList: Query
                 case QueryParamType.String: el = value; break;
                 case QueryParamType.Integer: el = parseInt(value); break;
                 case QueryParamType.Float: el = parseFloat(value); break;
+                case QueryParamType.Boolean: el = Boolean(+value); break;
             }
 
             if (p.validation) p.validation(el);
@@ -260,6 +264,7 @@ export function normalizeRestCommonParams(params: any): CommonQueryParamsInfo {
     return {
         model_nums: params.model_nums ? ('' + params.model_nums).split(',').map(n => n.trim()).filter(n => !!n).map(n => +n) : void 0,
         data_source: params.data_source,
+        copy_all_categories: Boolean(params.copy_all_categories),
         encoding: ('' + params.encoding).toLocaleLowerCase() === 'bcif' ? 'bcif' : 'cif'
     };
 }

+ 6 - 3
src/servers/model/server/jobs.ts

@@ -40,7 +40,8 @@ export interface JobEntry {
 
     queryDefinition: QueryDefinition,
     normalizedParams: any,
-    modelNums?: number[]
+    modelNums?: number[],
+    copyAllCategories: boolean
 }
 
 interface JobEntryDefinition<Name extends QueryName> {
@@ -48,7 +49,8 @@ interface JobEntryDefinition<Name extends QueryName> {
     entryId: string,
     queryName: Name,
     queryParams: QueryParams<Name>,
-    modelNums?: number[]
+    modelNums?: number[],
+    copyAllCategories: boolean
 }
 
 export function JobEntry<Name extends QueryName>(definition: JobEntryDefinition<Name>): JobEntry {
@@ -65,7 +67,8 @@ export function JobEntry<Name extends QueryName>(definition: JobEntryDefinition<
         entryId: definition.entryId,
         queryDefinition,
         normalizedParams,
-        modelNums: definition.modelNums
+        modelNums: definition.modelNums,
+        copyAllCategories: !!definition.copyAllCategories
     };
 }
 

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

@@ -163,7 +163,7 @@ async function resolveJobEntry(entry: JobEntry, structure: StructureWrapper, enc
         const queries = structures.map(s => entry.queryDefinition.query(entry.normalizedParams, s));
         const result: Structure[] = [];
         for (let i = 0; i < structures.length; i++) {
-            const s = await StructureSelection.unionStructure(StructureQuery.run(queries[i], structures[i], { timeoutMs: Config.queryTimeoutMs }));
+            const s = StructureSelection.unionStructure(StructureQuery.run(queries[i], structures[i], { timeoutMs: Config.queryTimeoutMs }));
             if (s.elementCount > 0) result.push(s);
         }
         perf.end('query');
@@ -178,9 +178,9 @@ async function resolveJobEntry(entry: JobEntry, structure: StructureWrapper, enc
         encoder.writeCategory(_model_server_result, entry);
         encoder.writeCategory(_model_server_params, entry);
 
-        if (entry.queryDefinition.filter) encoder.setFilter(entry.queryDefinition.filter);
-        if (result.length > 0) encode_mmCIF_categories(encoder, result);
-        if (entry.queryDefinition.filter) encoder.setFilter();
+        if (!entry.copyAllCategories && entry.queryDefinition.filter) encoder.setFilter(entry.queryDefinition.filter);
+        if (result.length > 0) encode_mmCIF_categories(encoder, result, { copyAllCategories: entry.copyAllCategories });
+        if (!entry.copyAllCategories && entry.queryDefinition.filter) encoder.setFilter();
         perf.end('encode');
 
         const stats: Stats = {

+ 3 - 3
src/servers/tsconfig.json → tsconfig.servers.json

@@ -16,8 +16,8 @@
         "noEmitHelpers": true,
         "jsx": "react",
         "lib": [ "es6", "dom", "esnext.asynciterable", "es2016" ],
-        "rootDir": "../../src",
-        "outDir": "../../lib/servers"
+        "rootDir": "src",
+        "outDir": "lib/servers"
     },
-    "include": [ "**/*" ]
+    "include": [ "src/servers/**/*" ]
 }