Files
enviPy-bayer/static/js/ketcher2/script/chem/molfile/molfile.js
2025-06-23 20:13:54 +02:00

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;