123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- /**
- * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
- */
- import { StringBuilder } from '../../../mol-util';
- import { Category } from '../cif/encoder';
- import { getCategoryInstanceData } from '../cif/encoder/util';
- import { LigandEncoder } from '../ligand-encoder';
- // specification: http://c4.cabrillo.edu/404/ctfile.pdf
- // SDF wraps MOL and allows for multiple molecules per file as well as additional properties
- export class MolEncoder extends LigandEncoder {
- _writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx) {
- // use separate builder because we still need to write Counts and Bonds line
- const ctab = StringBuilder.create();
- const bonds = StringBuilder.create();
- // write Atom block and gather data for Bonds and Charges
- const { instance, source } = getCategoryInstanceData(category, context);
- // write header
- const name = this.getName(instance, source);
- // 3rd lines must be present and can contain comments
- StringBuilder.writeSafe(this.builder, `${name}\n ${this.encoder}\n\n`);
- const atomMap = this.componentAtomData.entries.get(name)!;
- const bondMap = this.componentBondData.entries.get(name)!;
- // happens for the unknown ligands (UNL)
- if (!atomMap) throw Error(`The Chemical Component Dictionary doesn't hold any atom data for ${name}`);
- let bondCount = 0;
- let chiral = false;
- // traverse once to determine all actually present atoms
- const atoms = this.getAtoms(instance, source);
- atoms.forEach((atom1, label_atom_id1) => {
- const { index: i1, type_symbol: type_symbol1 } = atom1;
- const atomMapData1 = atomMap.map.get(label_atom_id1);
- if (!atomMapData1) {
- if (this.isHydrogen(type_symbol1)) {
- return;
- } else {
- throw Error(`Unknown atom ${label_atom_id1} for component ${name}`);
- }
- }
- const { charge, stereo_config } = atomMapData1;
- StringBuilder.writePadLeft(ctab, atom1.Cartn_x.toFixed(4), 10);
- StringBuilder.writePadLeft(ctab, atom1.Cartn_y.toFixed(4), 10);
- StringBuilder.writePadLeft(ctab, atom1.Cartn_z.toFixed(4), 10);
- StringBuilder.whitespace1(ctab);
- StringBuilder.writePadRight(ctab, atom1.type_symbol, 2);
- StringBuilder.writeSafe(ctab, ' 0');
- StringBuilder.writeIntegerPadLeft(ctab, this.mapCharge(charge), 3);
- StringBuilder.writeSafe(ctab, ' 0 0 0 0 0 0 0 0 0 0\n');
- if (stereo_config !== 'n') chiral = true;
- // no data for metal ions
- if (!bondMap?.map) return;
- bondMap.map.get(label_atom_id1)!.forEach((bond, label_atom_id2) => {
- const atom2 = atoms.get(label_atom_id2);
- if (!atom2) return;
- const { index: i2 } = atom2;
- if (i1 < i2) {
- const { order } = bond;
- StringBuilder.writeIntegerPadLeft(bonds, i1 + 1, 3);
- StringBuilder.writeIntegerPadLeft(bonds, i2 + 1, 3);
- StringBuilder.writeIntegerPadLeft(bonds, order, 3);
- StringBuilder.writeSafe(bonds, ' 0 0 0 0\n');
- bondCount++;
- }
- });
- });
- // write counts line
- StringBuilder.writeIntegerPadLeft(this.builder, atoms.size, 3);
- StringBuilder.writeIntegerPadLeft(this.builder, bondCount, 3);
- StringBuilder.writeSafe(this.builder, ` 0 0 ${chiral ? 1 : 0} 0 0 0 0 0 0\n`);
- StringBuilder.writeSafe(this.builder, StringBuilder.getString(ctab));
- StringBuilder.writeSafe(this.builder, StringBuilder.getString(bonds));
- StringBuilder.writeSafe(this.builder, 'M END\n');
- }
- private mapCharge(raw: number): number {
- // 0 = uncharged or value other than these, 1 = +3, 2 = +2, 3 = +1, 4 = doublet radical, 5 = -1, 6 = -2, 7 = -3
- switch (raw) {
- case 3: return 1;
- case 2: return 2;
- case 1: return 3;
- case -1: return 5;
- case -2: return 6;
- case -3: return 7;
- default: return 0;
- }
- }
- protected writeFullCategory<Ctx>(sb: StringBuilder, category: Category<Ctx>, context?: Ctx) {
- const { instance, source } = getCategoryInstanceData(category, context);
- const fields = instance.fields;
- const src = source[0];
- if (!src) return;
- const data = src.data;
- const it = src.keys();
- const key = it.move();
- for (let _f = 0; _f < fields.length; _f++) {
- const f = fields[_f]!;
- StringBuilder.writeSafe(sb, `> <${category.name}.${f.name}>\n`);
- const val = f.value(key, data, 0);
- StringBuilder.writeSafe(sb, val as string);
- StringBuilder.writeSafe(sb, '\n\n');
- }
- }
- encode() {
- // write meta-information, do so after ctab
- if (this.error || this.metaInformation) {
- StringBuilder.writeSafe(this.builder, StringBuilder.getString(this.meta));
- }
- // terminate file (needed for SDF only)
- if (!!this.terminator) {
- StringBuilder.writeSafe(this.builder, `${this.terminator}\n`);
- }
- this.encoded = true;
- }
- constructor(encoder: string, metaInformation: boolean, hydrogens: boolean, readonly terminator: string = '') {
- super(encoder, metaInformation, hydrogens);
- if (metaInformation && !terminator) {
- throw new Error('meta-information cannot be written for MOL files');
- }
- }
- }
|