diff --git a/src/mol-io/reader/3dg/parser.ts b/src/mol-io/reader/3dg/parser.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bc632a7144b305c656153c6c3202a731e0e46eea
--- /dev/null
+++ b/src/mol-io/reader/3dg/parser.ts
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ReaderResult as Result } from '../result'
+import { Task } from '../../../mol-task'
+import { parseCsv } from '../csv/parser';
+import { Column, Table } from '../../../mol-data/db';
+import { toTable } from '../cif/schema';
+
+import Schema = Column.Schema
+import { CsvTable } from '../csv/data-model';
+
+
+export const Schema3DG = {
+    /** Chromosome name */
+    chromosome: Schema.str,
+    /** Base position */
+    position: Schema.int,
+    /** X coordinate */
+    x: Schema.float,
+    /** Y coordinate */
+    y: Schema.float,
+    /** Z coordinate */
+    z: Schema.float,
+}
+export type Schema3DG = typeof Schema3DG
+
+export interface File3DG {
+    table: Table<Schema3DG>
+}
+
+const FieldNames = [ 'chromosome', 'position', 'x', 'y', 'z' ]
+
+function categoryFromTable(name: string, table: CsvTable) {
+    return {
+        name,
+        rowCount: table.rowCount,
+        fieldNames: FieldNames,
+        getField: (name: string) => {
+            return table.getColumn(FieldNames.indexOf(name).toString())
+        }
+    }
+}
+
+export function parse3DG(data: string) {
+    return Task.create<Result<File3DG>>('Parse 3DG', async ctx => {
+        const opts = { quote: '', comment: '#', delimiter: '\t', noColumnNames: true }
+        const csvFile = await parseCsv(data, opts).runInContext(ctx)
+        if (csvFile.isError) return Result.error(csvFile.message, csvFile.line)
+        const category = categoryFromTable('3dg', csvFile.result.table)
+        const table = toTable(Schema3DG, category)
+        return Result.success({ table })
+    });
+}
\ No newline at end of file
diff --git a/src/mol-io/reader/_spec/3dg.spec.ts b/src/mol-io/reader/_spec/3dg.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2a44987e991f93f462358c4d924d9d14b2307cad
--- /dev/null
+++ b/src/mol-io/reader/_spec/3dg.spec.ts
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { parse3DG } from '../3dg/parser';
+
+const basic3dgString = `1(mat)	1420000	0.791377837067	10.9947291355	-13.1882897693
+1(mat)	1440000	-0.268241283699	10.5200875887	-13.0896257278
+1(mat)	1460000	-1.3853075236	10.5513787498	-13.1440142173
+1(mat)	1480000	-1.55984101733	11.4340829129	-13.6026301209
+1(mat)	1500000	-0.770991778399	11.4758488546	-14.5881137222
+1(mat)	1520000	-0.0848245107875	12.2624690808	-14.354289628
+1(mat)	1540000	-0.458643807046	12.5985791771	-13.4701149287
+1(mat)	1560000	-0.810322906201	12.2461643989	-12.3172933413
+1(mat)	1580000	-2.08211172035	12.8886838656	-12.8742007778
+1(mat)	1600000	-3.52093948201	13.1850935438	-12.4118684428`
+
+describe('3dg reader', () => {
+    it('basic', async () => {
+        const parsed = await parse3DG(basic3dgString).run();
+        expect(parsed.isError).toBe(false)
+
+        if (parsed.isError) return;
+        const { chromosome, position, x, y, z } = parsed.result.table;
+        expect(chromosome.value(0)).toBe('1(mat)')
+        expect(position.value(1)).toBe(1440000)
+        expect(x.value(5)).toBe(-0.0848245107875)
+        expect(y.value(5)).toBe(12.2624690808)
+        expect(z.value(5)).toBe(-14.354289628)
+    });
+});
\ No newline at end of file
diff --git a/src/mol-model-formats/structure/3dg.ts b/src/mol-model-formats/structure/3dg.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f72aff712cfc7d96f4bd3d7a33bd2d30d7942f17
--- /dev/null
+++ b/src/mol-model-formats/structure/3dg.ts
@@ -0,0 +1,79 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Model } from '../../mol-model/structure/model';
+import { Task } from '../../mol-task';
+import { ModelFormat } from './format';
+import { _parse_mmCif } from './mmcif/parser';
+import { CifCategory, CifField } from '../../mol-io/reader/cif';
+import { Column } from '../../mol-data/db';
+import { mmCIF_Schema } from '../../mol-io/reader/cif/schema/mmcif';
+import { EntityBuilder } from './common/entity';
+import { File3DG } from '../../mol-io/reader/3dg/parser';
+import { fillSerial } from '../../mol-util/array';
+import { MoleculeType } from '../../mol-model/structure/model/types';
+
+function getCategories(table: File3DG['table']) {
+    const entityIds = new Array<string>(table._rowCount)
+    const entityBuilder = new EntityBuilder()
+
+    const seqIdStarts = table.position.toArray({ array: Uint32Array })
+    const seqIdEnds = new Uint32Array(table._rowCount)
+    const stride = seqIdStarts[1] - seqIdStarts[0]
+
+    const objectRadius = stride / 3500
+
+    for (let i = 0, il = table._rowCount; i < il; ++i) {
+        const chr = table.chromosome.value(i)
+        const entityId = entityBuilder.getEntityId(chr, MoleculeType.DNA, chr)
+        entityIds[i] = entityId
+        seqIdEnds[i] = seqIdStarts[i] + stride - 1
+    }
+
+    const ihm_sphere_obj_site: CifCategory.SomeFields<mmCIF_Schema['ihm_sphere_obj_site']> = {
+        id: CifField.ofNumbers(fillSerial(new Uint32Array(table._rowCount))),
+        entity_id: CifField.ofStrings(entityIds),
+        seq_id_begin: CifField.ofNumbers(seqIdStarts),
+        seq_id_end: CifField.ofNumbers(seqIdEnds),
+        asym_id: CifField.ofColumn(table.chromosome),
+
+        Cartn_x: CifField.ofNumbers(Column.mapToArray(table.x, x => x * 10, Float32Array)),
+        Cartn_y: CifField.ofNumbers(Column.mapToArray(table.y, y => y * 10, Float32Array)),
+        Cartn_z: CifField.ofNumbers(Column.mapToArray(table.z, z => z * 10, Float32Array)),
+
+        object_radius: CifField.ofColumn(Column.ofConst(objectRadius, table._rowCount, Column.Schema.float)),
+        rmsf: CifField.ofColumn(Column.ofConst(0, table._rowCount, Column.Schema.float)),
+        model_id: CifField.ofColumn(Column.ofConst(1, table._rowCount, Column.Schema.int)),
+    }
+
+    return {
+        entity: entityBuilder.getEntityCategory(),
+        ihm_model_list: CifCategory.ofFields('ihm_model_list', {
+            model_id: CifField.ofNumbers([1]),
+            model_name: CifField.ofStrings(['3DG Model']),
+        }),
+        ihm_sphere_obj_site: CifCategory.ofFields('ihm_sphere_obj_site', ihm_sphere_obj_site)
+    }
+}
+
+async function mmCifFrom3dg(file3dg: File3DG) {
+    const categories = getCategories(file3dg.table)
+
+    return {
+        header: '3DG',
+        categoryNames: Object.keys(categories),
+        categories
+    };
+}
+
+export function trajectoryFrom3DG(file3dg: File3DG): Task<Model.Trajectory> {
+    return Task.create('Parse 3DG', async ctx => {
+        await ctx.update('Converting to mmCIF');
+        const cif = await mmCifFrom3dg(file3dg);
+        const format = ModelFormat.mmCIF(cif);
+        return _parse_mmCif(format, ctx);
+    })
+}
diff --git a/src/mol-plugin/state/actions/data-format.ts b/src/mol-plugin/state/actions/data-format.ts
index dacd03fd7dff0b1da9669eaca97f201ca8e76c6a..f2d99a56c1b1e0c40880c5c6fe8989fe59794bba 100644
--- a/src/mol-plugin/state/actions/data-format.ts
+++ b/src/mol-plugin/state/actions/data-format.ts
@@ -12,7 +12,7 @@ import { PluginStateObject } from '../objects';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { Ccp4Provider, Dsn6Provider, DscifProvider } from './volume';
 import { StateTransforms } from '../transforms';
-import { MmcifProvider, PdbProvider, GroProvider } from './structure';
+import { MmcifProvider, PdbProvider, GroProvider, Provider3dg } from './structure';
 import msgpackDecode from '../../../mol-io/common/msgpack/decode'
 import { PlyProvider } from './shape';
 
@@ -55,6 +55,7 @@ export class DataFormatRegistry<D extends PluginStateObject.Data.Binary | Plugin
     }
 
     constructor() {
+        this.add('3dg', Provider3dg)
         this.add('ccp4', Ccp4Provider)
         this.add('dscif', DscifProvider)
         this.add('dsn6', Dsn6Provider)
diff --git a/src/mol-plugin/state/actions/structure.ts b/src/mol-plugin/state/actions/structure.ts
index c0f9c5a55a369cbc6e3d45391fdb2b6a8ef746e7..7cfa14dd6e73f839576efd7e4165edb99dfbd9ff 100644
--- a/src/mol-plugin/state/actions/structure.ts
+++ b/src/mol-plugin/state/actions/structure.ts
@@ -69,7 +69,23 @@ export const GroProvider: DataFormatProvider<any> = {
     }
 }
 
-type StructureFormat = 'pdb' | 'cif' | 'gro'
+export const Provider3dg: DataFormatProvider<any> = {
+    label: '3DG',
+    description: '3DG',
+    stringExtensions: ['3dg'],
+    binaryExtensions: [],
+    isApplicable: (info: FileInfo, data: string) => {
+        return info.ext === '3dg'
+    },
+    getDefaultBuilder: (ctx: PluginContext, data: StateBuilder.To<PluginStateObject.Data.String>, options: DataFormatBuilderOptions, state: State) => {
+        return Task.create('3DG default builder', async taskCtx => {
+            const traj = createModelTree(data, '3dg');
+            await state.updateTree(options.visuals ? createStructureTree(ctx, traj, false) : traj).runInContext(taskCtx)
+        })
+    }
+}
+
+type StructureFormat = 'pdb' | 'cif' | 'gro' | '3dg'
 
 //
 
@@ -219,6 +235,9 @@ export function createModelTree(b: StateBuilder.To<PluginStateObject.Data.Binary
         case 'gro':
             parsed = b.apply(StateTransforms.Model.TrajectoryFromGRO);
             break
+        case '3dg':
+            parsed = b.apply(StateTransforms.Model.TrajectoryFrom3DG);
+            break
         default:
             throw new Error('unsupported format')
     }
diff --git a/src/mol-plugin/state/transforms/model.ts b/src/mol-plugin/state/transforms/model.ts
index 2b41362937df3b6f08a5f29a2fb9ac4ace2542a7..5bc63f833d33c86bf468ac312c43e1c4e72d8275 100644
--- a/src/mol-plugin/state/transforms/model.ts
+++ b/src/mol-plugin/state/transforms/model.ts
@@ -26,11 +26,14 @@ import { shapeFromPly } from '../../../mol-model-formats/shape/ply';
 import { SymmetryOperator } from '../../../mol-math/geometry';
 import { ensureSecondaryStructure } from './helpers';
 import { Script } from '../../../mol-script/script';
+import { parse3DG } from '../../../mol-io/reader/3dg/parser';
+import { trajectoryFrom3DG } from '../../../mol-model-formats/structure/3dg';
 
 export { TrajectoryFromBlob };
 export { TrajectoryFromMmCif };
 export { TrajectoryFromPDB };
 export { TrajectoryFromGRO };
+export { TrajectoryFrom3DG };
 export { ModelFromTrajectory };
 export { StructureFromTrajectory };
 export { StructureFromModel };
@@ -137,6 +140,24 @@ const TrajectoryFromGRO = PluginStateTransform.BuiltIn({
     }
 });
 
+type TrajectoryFrom3DG = typeof TrajectoryFrom3DG
+const TrajectoryFrom3DG = PluginStateTransform.BuiltIn({
+    name: 'trajectory-from-3dg',
+    display: { name: 'Parse 3DG', description: 'Parse 3DG string and create trajectory.' },
+    from: [SO.Data.String],
+    to: SO.Molecule.Trajectory
+})({
+    apply({ a }) {
+        return Task.create('Parse 3DG', async ctx => {
+            const parsed = await parse3DG(a.data).runInContext(ctx);
+            if (parsed.isError) throw new Error(parsed.message);
+            const models = await trajectoryFrom3DG(parsed.result).runInContext(ctx);
+            const props = { label: `${models[0].entry}`, description: `${models.length} model${models.length === 1 ? '' : 's'}` };
+            return new SO.Molecule.Trajectory(models, props);
+        });
+    }
+});
+
 const plus1 = (v: number) => v + 1, minus1 = (v: number) => v - 1;
 type ModelFromTrajectory = typeof ModelFromTrajectory
 const ModelFromTrajectory = PluginStateTransform.BuiltIn({