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

275 lines
8.0 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 Vec2 = require('../../util/vec2');
var Struct = require('./../struct/index');
function paddedNum(number, width, precision) {
var numStr = number.toFixed(precision || 0).replace(',', '.'); // Really need to replace?
if (numStr.length > width)
throw new Error('number does not fit');
return numStr.padStart(width);
}
function parseDecimalInt(str) {
/* reader */
var val = parseInt(str, 10);
return isNaN(val) ? 0 : val;
}
function partitionLine(/* string*/ str, /* array of int*/ parts, /* bool*/ withspace) {
/* reader */
var res = [];
for (var i = 0, shift = 0; i < parts.length; ++i) {
res.push(str.slice(shift, shift + parts[i]));
if (withspace)
shift++;
shift += parts[i];
}
return res;
}
function partitionLineFixed(/* string*/ str, /* int*/ itemLength, /* bool*/ withspace) {
/* reader */
var res = [];
for (var shift = 0; shift < str.length; shift += itemLength) {
res.push(str.slice(shift, shift + itemLength));
if (withspace)
shift++;
}
return res;
}
var fmtInfo = {
bondTypeMap: {
1: Struct.Bond.PATTERN.TYPE.SINGLE,
2: Struct.Bond.PATTERN.TYPE.DOUBLE,
3: Struct.Bond.PATTERN.TYPE.TRIPLE,
4: Struct.Bond.PATTERN.TYPE.AROMATIC,
5: Struct.Bond.PATTERN.TYPE.SINGLE_OR_DOUBLE,
6: Struct.Bond.PATTERN.TYPE.SINGLE_OR_AROMATIC,
7: Struct.Bond.PATTERN.TYPE.DOUBLE_OR_AROMATIC,
8: Struct.Bond.PATTERN.TYPE.ANY
},
bondStereoMap: {
0: Struct.Bond.PATTERN.STEREO.NONE,
1: Struct.Bond.PATTERN.STEREO.UP,
4: Struct.Bond.PATTERN.STEREO.EITHER,
6: Struct.Bond.PATTERN.STEREO.DOWN,
3: Struct.Bond.PATTERN.STEREO.CIS_TRANS
},
v30bondStereoMap: {
0: Struct.Bond.PATTERN.STEREO.NONE,
1: Struct.Bond.PATTERN.STEREO.UP,
2: Struct.Bond.PATTERN.STEREO.EITHER,
3: Struct.Bond.PATTERN.STEREO.DOWN
},
bondTopologyMap: {
0: Struct.Bond.PATTERN.TOPOLOGY.EITHER,
1: Struct.Bond.PATTERN.TOPOLOGY.RING,
2: Struct.Bond.PATTERN.TOPOLOGY.CHAIN
},
countsLinePartition: [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6],
atomLinePartition: [10, 10, 10, 1, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
bondLinePartition: [3, 3, 3, 3, 3, 3, 3],
atomListHeaderPartition: [3, 1, 1, 4, 1, 1],
atomListHeaderLength: 11, // = atomListHeaderPartition.reduce(function(a,b) { return a + b; }, 0)
atomListHeaderItemLength: 4,
chargeMap: [0, +3, +2, +1, 0, -1, -2, -3],
valenceMap: [undefined, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 0],
implicitHydrogenMap: [undefined, 0, 1, 2, 3, 4],
v30atomPropMap: {
CHG: 'charge',
RAD: 'radical',
MASS: 'isotope',
VAL: 'explicitValence',
HCOUNT: 'hCount',
INVRET: 'invRet',
SUBST: 'substitutionCount',
UNSAT: 'unsaturatedAtom',
RBCNT: 'ringBondCount'
},
rxnItemsPartition: [3, 3, 3]
};
var FRAGMENT = {
NONE: 0,
REACTANT: 1,
PRODUCT: 2,
AGENT: 3
};
var SHOULD_REACTION_FRAGMENT_RELAYOUT = true;
var SHOULD_RESCALE_MOLECULES = true;
function rxnMerge(mols, nReactants, nProducts) /* Struct */ { // eslint-disable-line max-statements
/* reader */
var ret = new Struct();
var bbReact = [],
bbAgent = [],
bbProd = [];
var molReact = [],
molAgent = [],
molProd = [];
var j;
var bondLengthData = { cnt: 0, totalLength: 0 };
for (j = 0; j < mols.length; ++j) {
var mol = mols[j];
var bondLengthDataMol = mol.getBondLengthData();
bondLengthData.cnt += bondLengthDataMol.cnt;
bondLengthData.totalLength += bondLengthDataMol.totalLength;
}
if (SHOULD_RESCALE_MOLECULES) {
var avgBondLength = 1 / (bondLengthData.cnt == 0 ? 1 : bondLengthData.totalLength / bondLengthData.cnt);
for (j = 0; j < mols.length; ++j) {
mol = mols[j];
mol.scale(avgBondLength);
}
}
for (j = 0; j < mols.length; ++j) {
mol = mols[j];
var bb = mol.getCoordBoundingBoxObj();
if (!bb)
continue; // eslint-disable-line no-continue
var fragmentType = (j < nReactants ? FRAGMENT.REACTANT : // eslint-disable-line no-nested-ternary
(j < nReactants + nProducts ? FRAGMENT.PRODUCT :
FRAGMENT.AGENT));
if (fragmentType == FRAGMENT.REACTANT) {
bbReact.push(bb);
molReact.push(mol);
} else if (fragmentType == FRAGMENT.AGENT) {
bbAgent.push(bb);
molAgent.push(mol);
} else if (fragmentType == FRAGMENT.PRODUCT) {
bbProd.push(bb);
molProd.push(mol);
}
mol.atoms.each(function (aid, atom) {
atom.rxnFragmentType = fragmentType;
});
}
function shiftMol(ret, mol, bb, xorig, over) { // eslint-disable-line max-params
var d = new Vec2(xorig - bb.min.x, over ? 1 - bb.min.y : -(bb.min.y + bb.max.y) / 2);
mol.atoms.each(function (aid, atom) {
atom.pp.add_(d); // eslint-disable-line no-underscore-dangle
});
mol.sgroups.each(function (id, item) {
if (item.pp)
item.pp.add_(d); // eslint-disable-line no-underscore-dangle
});
bb.min.add_(d); // eslint-disable-line no-underscore-dangle
bb.max.add_(d); // eslint-disable-line no-underscore-dangle
mol.mergeInto(ret);
return bb.max.x - bb.min.x;
}
if (SHOULD_REACTION_FRAGMENT_RELAYOUT) {
// reaction fragment layout
var xorig = 0;
for (j = 0; j < molReact.length; ++j)
xorig += shiftMol(ret, molReact[j], bbReact[j], xorig, false) + 2.0;
xorig += 2.0;
for (j = 0; j < molAgent.length; ++j)
xorig += shiftMol(ret, molAgent[j], bbAgent[j], xorig, true) + 2.0;
xorig += 2.0;
for (j = 0; j < molProd.length; ++j)
xorig += shiftMol(ret, molProd[j], bbProd[j], xorig, false) + 2.0;
} else {
for (j = 0; j < molReact.length; ++j)
molReact[j].mergeInto(ret);
for (j = 0; j < molAgent.length; ++j)
molAgent[j].mergeInto(ret);
for (j = 0; j < molProd.length; ++j)
molProd[j].mergeInto(ret);
}
var bb1;
var bb2;
var x;
var y;
var bbReactAll = null;
var bbProdAll = null;
for (j = 0; j < bbReact.length - 1; ++j) {
bb1 = bbReact[j];
bb2 = bbReact[j + 1];
x = (bb1.max.x + bb2.min.x) / 2;
y = (bb1.max.y + bb1.min.y + bb2.max.y + bb2.min.y) / 4;
ret.rxnPluses.add(new Struct.RxnPlus({ pp: new Vec2(x, y) }));
}
for (j = 0; j < bbReact.length; ++j) {
if (j == 0) {
bbReactAll = {};
bbReactAll.max = new Vec2(bbReact[j].max);
bbReactAll.min = new Vec2(bbReact[j].min);
} else {
bbReactAll.max = Vec2.max(bbReactAll.max, bbReact[j].max);
bbReactAll.min = Vec2.min(bbReactAll.min, bbReact[j].min);
}
}
for (j = 0; j < bbProd.length - 1; ++j) {
bb1 = bbProd[j];
bb2 = bbProd[j + 1];
x = (bb1.max.x + bb2.min.x) / 2;
y = (bb1.max.y + bb1.min.y + bb2.max.y + bb2.min.y) / 4;
ret.rxnPluses.add(new Struct.RxnPlus({ pp: new Vec2(x, y) }));
}
for (j = 0; j < bbProd.length; ++j) {
if (j == 0) {
bbProdAll = {};
bbProdAll.max = new Vec2(bbProd[j].max);
bbProdAll.min = new Vec2(bbProd[j].min);
} else {
bbProdAll.max = Vec2.max(bbProdAll.max, bbProd[j].max);
bbProdAll.min = Vec2.min(bbProdAll.min, bbProd[j].min);
}
}
bb1 = bbReactAll;
bb2 = bbProdAll;
if (!bb1 && !bb2) {
ret.rxnArrows.add(new Struct.RxnArrow({ pp: new Vec2(0, 0) }));
} else {
var v1 = bb1 ? new Vec2(bb1.max.x, (bb1.max.y + bb1.min.y) / 2) : null;
var v2 = bb2 ? new Vec2(bb2.min.x, (bb2.max.y + bb2.min.y) / 2) : null;
var defaultOffset = 3;
if (!v1)
v1 = new Vec2(v2.x - defaultOffset, v2.y);
if (!v2)
v2 = new Vec2(v1.x + defaultOffset, v1.y);
ret.rxnArrows.add(new Struct.RxnArrow({ pp: Vec2.lc2(v1, 0.5, v2, 0.5) }));
}
ret.isReaction = true;
return ret;
}
module.exports = {
fmtInfo: fmtInfo,
paddedNum: paddedNum,
parseDecimalInt: parseDecimalInt,
partitionLine: partitionLine,
partitionLineFixed: partitionLineFixed,
rxnMerge: rxnMerge
};