forked from enviPath/enviPy
489 lines
14 KiB
JavaScript
489 lines
14 KiB
JavaScript
/****************************************************************************
|
|
* Copyright 2017 EPAM Systems
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
***************************************************************************/
|
|
|
|
var element = require('./../element');
|
|
|
|
var common = require('./common');
|
|
var utils = require('./utils');
|
|
|
|
function Molfile(v3000) {
|
|
/* reader */
|
|
/* saver */
|
|
this.molecule = null;
|
|
this.molfile = null;
|
|
this.v3000 = v3000 || false;
|
|
}
|
|
|
|
Molfile.prototype.parseCTFile = function (molfileLines) {
|
|
var ret = null;
|
|
if (molfileLines[0].search('\\$RXN') == 0)
|
|
ret = common.parseRxn(molfileLines);
|
|
else
|
|
ret = common.parseMol(molfileLines);
|
|
ret.initHalfBonds();
|
|
ret.initNeighbors();
|
|
ret.markFragments();
|
|
return ret;
|
|
};
|
|
|
|
Molfile.prototype.prepareSGroups = function (skipErrors, preserveIndigoDesc) {
|
|
var mol = this.molecule;
|
|
var toRemove = [];
|
|
var errors = 0;
|
|
|
|
this.molecule.sGroupForest.getSGroupsBFS().reverse().forEach(function (id) {
|
|
var sgroup = mol.sgroups.get(id);
|
|
var errorIgnore = false;
|
|
|
|
try {
|
|
common.prepareForSaving[sgroup.type](sgroup, mol);
|
|
} catch (ex) {
|
|
if (!skipErrors || typeof (ex.id) != 'number')
|
|
throw ex;
|
|
errorIgnore = true;
|
|
}
|
|
/* eslint-disable no-mixed-operators*/
|
|
if (errorIgnore ||
|
|
!preserveIndigoDesc && /^INDIGO_.+_DESC$/i.test(sgroup.data.fieldName)) {
|
|
/* eslint-enable no-mixed-operators*/
|
|
errors += errorIgnore;
|
|
toRemove.push(sgroup.id);
|
|
}
|
|
}, this);
|
|
if (errors)
|
|
throw new Error('WARNING: ' + errors + ' invalid S-groups were detected. They will be omitted.');
|
|
|
|
for (var i = 0; i < toRemove.length; ++i)
|
|
mol.sGroupDelete(toRemove[i]);
|
|
return mol;
|
|
};
|
|
|
|
Molfile.prototype.getCTab = function (molecule, rgroups) {
|
|
/* saver */
|
|
this.molecule = molecule.clone();
|
|
this.molfile = '';
|
|
this.writeCTab2000(rgroups);
|
|
return this.molfile;
|
|
};
|
|
|
|
Molfile.prototype.saveMolecule = function (molecule, skipSGroupErrors, norgroups, preserveIndigoDesc) { // eslint-disable-line max-statements
|
|
/* saver */
|
|
this.reaction = molecule.rxnArrows.count() > 0;
|
|
if (molecule.rxnArrows.count() > 1)
|
|
throw new Error('Reaction may not contain more than one arrow');
|
|
this.molfile = '' + molecule.name;
|
|
if (this.reaction) {
|
|
if (molecule.rgroups.count() > 0)
|
|
throw new Error('Unable to save the structure - reactions with r-groups are not supported at the moment');
|
|
var components = molecule.getComponents();
|
|
|
|
var reactants = components.reactants;
|
|
var products = components.products;
|
|
var all = reactants.concat(products);
|
|
this.molfile = '$RXN\n\n\n\n' +
|
|
utils.paddedNum(reactants.length, 3) +
|
|
utils.paddedNum(products.length, 3) +
|
|
utils.paddedNum(0, 3) + '\n';
|
|
for (var i = 0; i < all.length; ++i) {
|
|
var saver = new Molfile(false);
|
|
var submol = molecule.clone(all[i], null, true);
|
|
var molfile = saver.saveMolecule(submol, false, true);
|
|
this.molfile += '$MOL\n' + molfile;
|
|
}
|
|
return this.molfile;
|
|
}
|
|
|
|
if (molecule.rgroups.count() > 0) {
|
|
if (norgroups) {
|
|
molecule = molecule.getScaffold();
|
|
} else {
|
|
var scaffold = new Molfile(false).getCTab(molecule.getScaffold(), molecule.rgroups);
|
|
this.molfile = '$MDL REV 1\n$MOL\n$HDR\n\n\n\n$END HDR\n';
|
|
this.molfile += '$CTAB\n' + scaffold + '$END CTAB\n';
|
|
|
|
molecule.rgroups.each(function (rgid, rg) {
|
|
this.molfile += '$RGP\n';
|
|
this.writePaddedNumber(rgid, 3);
|
|
this.molfile += '\n';
|
|
rg.frags.each(function (fnum, fid) {
|
|
var group = new Molfile(false).getCTab(molecule.getFragment(fid));
|
|
this.molfile += '$CTAB\n' + group + '$END CTAB\n';
|
|
}, this);
|
|
this.molfile += '$END RGP\n';
|
|
}, this);
|
|
this.molfile += '$END MOL\n';
|
|
|
|
return this.molfile;
|
|
}
|
|
}
|
|
|
|
this.molecule = molecule.clone();
|
|
|
|
this.prepareSGroups(skipSGroupErrors, preserveIndigoDesc);
|
|
|
|
this.writeHeader();
|
|
|
|
// TODO: saving to V3000
|
|
this.writeCTab2000();
|
|
|
|
return this.molfile;
|
|
};
|
|
|
|
Molfile.prototype.writeHeader = function () {
|
|
/* saver */
|
|
|
|
var date = new Date();
|
|
|
|
this.writeCR(); // TODO: write structure name
|
|
this.writeWhiteSpace(2);
|
|
this.write('Ketcher');
|
|
this.writeWhiteSpace();
|
|
this.writeCR(((date.getMonth() + 1) + '').padStart(2) + (date.getDate() + '').padStart(2) + ((date.getFullYear() % 100) + '').padStart(2) +
|
|
(date.getHours() + '').padStart(2) + (date.getMinutes() + '').padStart(2) + '2D 1 1.00000 0.00000 0');
|
|
this.writeCR();
|
|
};
|
|
|
|
Molfile.prototype.write = function (str) {
|
|
/* saver */
|
|
this.molfile += str;
|
|
};
|
|
|
|
Molfile.prototype.writeCR = function (str) {
|
|
/* saver */
|
|
if (arguments.length == 0)
|
|
str = '';
|
|
|
|
this.molfile += str + '\n';
|
|
};
|
|
|
|
Molfile.prototype.writeWhiteSpace = function (length) {
|
|
/* saver */
|
|
|
|
if (arguments.length == 0)
|
|
length = 1;
|
|
|
|
this.write(' '.repeat(Math.max(length, 0)));
|
|
};
|
|
|
|
Molfile.prototype.writePadded = function (str, width) {
|
|
/* saver */
|
|
this.write(str);
|
|
this.writeWhiteSpace(width - str.length);
|
|
};
|
|
|
|
Molfile.prototype.writePaddedNumber = function (number, width) {
|
|
/* saver */
|
|
|
|
var str = (number - 0).toString();
|
|
|
|
this.writeWhiteSpace(width - str.length);
|
|
this.write(str);
|
|
};
|
|
|
|
Molfile.prototype.writePaddedFloat = function (number, width, precision) {
|
|
/* saver */
|
|
|
|
this.write(utils.paddedNum(number, width, precision));
|
|
};
|
|
|
|
Molfile.prototype.writeCTab2000Header = function () {
|
|
/* saver */
|
|
|
|
this.writePaddedNumber(this.molecule.atoms.count(), 3);
|
|
this.writePaddedNumber(this.molecule.bonds.count(), 3);
|
|
|
|
this.writePaddedNumber(0, 3);
|
|
this.writeWhiteSpace(3);
|
|
this.writePaddedNumber(this.molecule.isChiral ? 1 : 0, 3);
|
|
this.writePaddedNumber(0, 3);
|
|
this.writeWhiteSpace(12);
|
|
this.writePaddedNumber(999, 3);
|
|
this.writeCR(' V2000');
|
|
};
|
|
|
|
Molfile.prototype.writeCTab2000 = function (rgroups) { // eslint-disable-line max-statements
|
|
/* saver */
|
|
this.writeCTab2000Header();
|
|
|
|
this.mapping = {};
|
|
var i = 1;
|
|
|
|
/* eslint-disable camelcase*/
|
|
var atomList_list = [];
|
|
var atomProps_list = [];
|
|
/* eslint-enable camel-case*/
|
|
this.molecule.atoms.each(function (id, atom) { // eslint-disable-line max-statements
|
|
this.writePaddedFloat(atom.pp.x, 10, 4);
|
|
this.writePaddedFloat(-atom.pp.y, 10, 4);
|
|
this.writePaddedFloat(atom.pp.z, 10, 4);
|
|
this.writeWhiteSpace();
|
|
|
|
var label = atom.label;
|
|
if (atom.atomList != null) {
|
|
label = 'L';
|
|
atomList_list.push(id);
|
|
} else if (atom['pseudo']) {
|
|
if (atom['pseudo'].length > 3) {
|
|
label = 'A';
|
|
atomProps_list.push({ id: id, value: "'" + atom['pseudo'] + "'" });
|
|
}
|
|
} else if (atom['alias']) {
|
|
atomProps_list.push({ id: id, value: atom['alias'] });
|
|
} else if (!element.map[label] && ['A', 'Q', 'X', '*', 'R#'].indexOf(label) == -1) { // search in generics?
|
|
label = 'C';
|
|
atomProps_list.push({ id: id, value: atom.label });
|
|
}
|
|
this.writePadded(label, 3);
|
|
this.writePaddedNumber(0, 2);
|
|
this.writePaddedNumber(0, 3);
|
|
this.writePaddedNumber(0, 3);
|
|
|
|
if (typeof atom.hCount === "undefined")
|
|
atom.hCount = 0;
|
|
this.writePaddedNumber(atom.hCount, 3);
|
|
|
|
if (typeof atom.stereoCare === "undefined")
|
|
atom.stereoCare = 0;
|
|
this.writePaddedNumber(atom.stereoCare, 3);
|
|
|
|
this.writePaddedNumber(atom.explicitValence < 0 ? 0 : (atom.explicitValence == 0 ? 15 : atom.explicitValence), 3); // eslint-disable-line no-nested-ternary
|
|
|
|
this.writePaddedNumber(0, 3);
|
|
this.writePaddedNumber(0, 3);
|
|
this.writePaddedNumber(0, 3);
|
|
|
|
if (typeof atom.aam === "undefined")
|
|
atom.aam = 0;
|
|
this.writePaddedNumber(atom.aam, 3);
|
|
|
|
if (typeof atom.invRet === "undefined")
|
|
atom.invRet = 0;
|
|
this.writePaddedNumber(atom.invRet, 3);
|
|
|
|
if (typeof atom.exactChangeFlag === "undefined")
|
|
atom.exactChangeFlag = 0;
|
|
this.writePaddedNumber(atom.exactChangeFlag, 3);
|
|
|
|
this.writeCR();
|
|
|
|
this.mapping[id] = i;
|
|
i++;
|
|
}, this);
|
|
|
|
this.bondMapping = {};
|
|
i = 1;
|
|
this.molecule.bonds.each(function (id, bond) {
|
|
this.bondMapping[id] = i++;
|
|
this.writePaddedNumber(this.mapping[bond.begin], 3);
|
|
this.writePaddedNumber(this.mapping[bond.end], 3);
|
|
this.writePaddedNumber(bond.type, 3);
|
|
|
|
if (typeof bond.stereo === "undefined")
|
|
bond.stereo = 0;
|
|
this.writePaddedNumber(bond.stereo, 3);
|
|
|
|
this.writePadded(bond.xxx, 3);
|
|
|
|
if (typeof bond.topology === "undefined")
|
|
bond.topology = 0;
|
|
this.writePaddedNumber(bond.topology, 3);
|
|
|
|
if (typeof bond.reactingCenterStatus === "undefined")
|
|
bond.reactingCenterStatus = 0;
|
|
this.writePaddedNumber(bond.reactingCenterStatus, 3);
|
|
|
|
this.writeCR();
|
|
}, this);
|
|
|
|
while (atomProps_list.length > 0) {
|
|
this.write('A ');
|
|
this.writePaddedNumber(atomProps_list[0].id + 1, 3);
|
|
this.writeCR();
|
|
this.writeCR(atomProps_list[0].value);
|
|
atomProps_list.splice(0, 1);
|
|
}
|
|
|
|
var chargeList = [];
|
|
var isotopeList = [];
|
|
var radicalList = [];
|
|
var rglabelList = [];
|
|
var rglogicList = [];
|
|
var aplabelList = [];
|
|
var rbcountList = [];
|
|
var unsaturatedList = [];
|
|
var substcountList = [];
|
|
|
|
this.molecule.atoms.each(function (id, atom) {
|
|
if (atom.charge != 0)
|
|
chargeList.push([id, atom.charge]);
|
|
if (atom.isotope != 0)
|
|
isotopeList.push([id, atom.isotope]);
|
|
if (atom.radical != 0)
|
|
radicalList.push([id, atom.radical]);
|
|
if (atom.rglabel != null && atom.label == 'R#') { // TODO need to force rglabel=null when label is not 'R#'
|
|
for (var rgi = 0; rgi < 32; rgi++)
|
|
if (atom.rglabel & (1 << rgi)) rglabelList.push([id, rgi + 1]);
|
|
}
|
|
if (atom.attpnt != null)
|
|
aplabelList.push([id, atom.attpnt]);
|
|
if (atom.ringBondCount != 0)
|
|
rbcountList.push([id, atom.ringBondCount]);
|
|
if (atom.substitutionCount != 0)
|
|
substcountList.push([id, atom.substitutionCount]);
|
|
if (atom.unsaturatedAtom != 0)
|
|
unsaturatedList.push([id, atom.unsaturatedAtom]);
|
|
});
|
|
|
|
if (rgroups) {
|
|
rgroups.each(function (rgid, rg) {
|
|
if (rg.resth || rg.ifthen > 0 || rg.range.length > 0) {
|
|
var line = ' 1 ' +
|
|
utils.paddedNum(rgid, 3) + ' ' +
|
|
utils.paddedNum(rg.ifthen, 3) + ' ' +
|
|
utils.paddedNum(rg.resth ? 1 : 0, 3) + ' ' + rg.range;
|
|
rglogicList.push(line);
|
|
}
|
|
});
|
|
}
|
|
|
|
function writeAtomPropList(propId, values) {
|
|
while (values.length > 0) {
|
|
var part = [];
|
|
|
|
while (values.length > 0 && part.length < 8) {
|
|
part.push(values[0]);
|
|
values.splice(0, 1);
|
|
}
|
|
|
|
this.write(propId);
|
|
this.writePaddedNumber(part.length, 3);
|
|
|
|
part.forEach(function (value) {
|
|
this.writeWhiteSpace();
|
|
this.writePaddedNumber(this.mapping[value[0]], 3);
|
|
this.writeWhiteSpace();
|
|
this.writePaddedNumber(value[1], 3);
|
|
}, this);
|
|
|
|
this.writeCR();
|
|
}
|
|
}
|
|
|
|
writeAtomPropList.call(this, 'M CHG', chargeList);
|
|
writeAtomPropList.call(this, 'M ISO', isotopeList);
|
|
writeAtomPropList.call(this, 'M RAD', radicalList);
|
|
writeAtomPropList.call(this, 'M RGP', rglabelList);
|
|
for (var j = 0; j < rglogicList.length; ++j)
|
|
this.write('M LOG' + rglogicList[j] + '\n');
|
|
|
|
writeAtomPropList.call(this, 'M APO', aplabelList);
|
|
writeAtomPropList.call(this, 'M RBC', rbcountList);
|
|
writeAtomPropList.call(this, 'M SUB', substcountList);
|
|
writeAtomPropList.call(this, 'M UNS', unsaturatedList);
|
|
|
|
if (atomList_list.length > 0) {
|
|
for (j = 0; j < atomList_list.length; ++j) {
|
|
var aid = atomList_list[j];
|
|
var atomList = this.molecule.atoms.get(aid).atomList;
|
|
this.write('M ALS');
|
|
this.writePaddedNumber(aid + 1, 4);
|
|
this.writePaddedNumber(atomList.ids.length, 3);
|
|
this.writeWhiteSpace();
|
|
this.write(atomList.notList ? 'T' : 'F');
|
|
|
|
var labelList = atomList.labelList();
|
|
for (var k = 0; k < labelList.length; ++k) {
|
|
this.writeWhiteSpace();
|
|
this.writePadded(labelList[k], 3);
|
|
}
|
|
this.writeCR();
|
|
}
|
|
}
|
|
|
|
var sgmap = {};
|
|
var cnt = 1;
|
|
var sgmapback = {};
|
|
var sgorder = this.molecule.sGroupForest.getSGroupsBFS();
|
|
sgorder.forEach(function (id) {
|
|
sgmapback[cnt] = id;
|
|
sgmap[id] = cnt++;
|
|
}, this);
|
|
for (var q = 1; q < cnt; ++q) { // each group on its own
|
|
var id = sgmapback[q];
|
|
var sgroup = this.molecule.sgroups.get(id);
|
|
this.write('M STY');
|
|
this.writePaddedNumber(1, 3);
|
|
this.writeWhiteSpace(1);
|
|
this.writePaddedNumber(q, 3);
|
|
this.writeWhiteSpace(1);
|
|
this.writePadded(sgroup.type, 3);
|
|
this.writeCR();
|
|
|
|
// TODO: write subtype, M SST
|
|
|
|
this.write('M SLB');
|
|
this.writePaddedNumber(1, 3);
|
|
this.writeWhiteSpace(1);
|
|
this.writePaddedNumber(q, 3);
|
|
this.writeWhiteSpace(1);
|
|
this.writePaddedNumber(q, 3);
|
|
this.writeCR();
|
|
|
|
var parentid = this.molecule.sGroupForest.parent.get(id);
|
|
if (parentid >= 0) {
|
|
this.write('M SPL');
|
|
this.writePaddedNumber(1, 3);
|
|
this.writeWhiteSpace(1);
|
|
this.writePaddedNumber(q, 3);
|
|
this.writeWhiteSpace(1);
|
|
this.writePaddedNumber(sgmap[parentid], 3);
|
|
this.writeCR();
|
|
}
|
|
|
|
// connectivity
|
|
if (sgroup.type == 'SRU' && sgroup.data.connectivity) {
|
|
var connectivity = '';
|
|
connectivity += ' ';
|
|
connectivity += q.toString().padStart(3);
|
|
connectivity += ' ';
|
|
connectivity += (sgroup.data.connectivity || '').padEnd(3);
|
|
this.write('M SCN');
|
|
this.writePaddedNumber(1, 3);
|
|
this.write(connectivity.toUpperCase());
|
|
this.writeCR();
|
|
}
|
|
|
|
if (sgroup.type == 'SRU') {
|
|
this.write('M SMT ');
|
|
this.writePaddedNumber(q, 3);
|
|
this.writeWhiteSpace();
|
|
this.write(sgroup.data.subscript || 'n');
|
|
this.writeCR();
|
|
}
|
|
|
|
this.writeCR(common.saveToMolfile[sgroup.type](sgroup, this.molecule, sgmap, this.mapping, this.bondMapping));
|
|
}
|
|
|
|
// TODO: write M APO
|
|
// TODO: write M AAL
|
|
// TODO: write M RGP
|
|
// TODO: write M LOG
|
|
|
|
this.writeCR('M END');
|
|
};
|
|
|
|
module.exports = Molfile;
|