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

384 lines
10 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 Box2Abs = require('../../util/box2abs');
var Set = require('../../util/set');
var Vec2 = require('../../util/vec2');
var Atom = require('./atom');
var Bond = require('./bond');
function SGroup(type) { // eslint-disable-line max-statements
console.assert(type && type in SGroup.TYPES, 'Invalid or unsupported s-group type');
this.type = type;
this.id = -1;
this.label = -1;
this.bracketBox = null;
this.bracketDir = new Vec2(1, 0);
this.areas = [];
this.highlight = false;
this.highlighting = null;
this.selected = false;
this.selectionPlate = null;
this.atoms = [];
this.patoms = [];
this.bonds = [];
this.xBonds = [];
this.neiAtoms = [];
this.pp = null;
this.data = {
mul: 1, // multiplication count for MUL group
connectivity: 'ht', // head-to-head, head-to-tail or either-unknown
name: '',
subscript: 'n',
// data s-group fields
attached: false,
absolute: true,
showUnits: false,
nCharsToDisplay: -1,
tagChar: '',
daspPos: 1,
fieldType: 'F',
fieldName: '',
fieldValue: '',
units: '',
query: '',
queryOp: ''
};
}
SGroup.TYPES = {
MUL: 1,
SRU: 2,
SUP: 3,
DAT: 4,
GEN: 5
};
// TODO: these methods should be overridden
// and should only accept valid attributes for each S-group type.
// The attributes should be accessed via these methods only and not directly through this.data.
// stub
SGroup.prototype.getAttr = function (attr) {
return this.data[attr];
};
// TODO: should be group-specific
SGroup.prototype.getAttrs = function () {
var attrs = {};
for (var attr in this.data) {
if (this.data.hasOwnProperty(attr))
attrs[attr] = this.data[attr];
}
return attrs;
};
// stub
SGroup.prototype.setAttr = function (attr, value) {
var oldValue = this.data[attr];
this.data[attr] = value;
return oldValue;
};
// stub
SGroup.prototype.checkAttr = function (attr, value) {
return this.data[attr] == value;
};
// SGroup.numberArrayToString = function (numbers, map) {
// var str = util.stringPadded(numbers.length, 3);
// for (var i = 0; i < numbers.length; ++i) {
// str += ' ' + util.stringPadded(map[numbers[i]], 3);
// }
// return str;
// };
SGroup.filterAtoms = function (atoms, map) {
var newAtoms = [];
for (var i = 0; i < atoms.length; ++i) {
var aid = atoms[i];
if (typeof (map[aid]) !== 'number')
newAtoms.push(aid);
else if (map[aid] >= 0)
newAtoms.push(map[aid]);
else
newAtoms.push(-1);
}
return newAtoms;
};
SGroup.removeNegative = function (atoms) {
var newAtoms = [];
for (var j = 0; j < atoms.length; ++j) {
if (atoms[j] >= 0)
newAtoms.push(atoms[j]);
}
return newAtoms;
};
SGroup.filter = function (mol, sg, atomMap) {
sg.atoms = SGroup.removeNegative(SGroup.filterAtoms(sg.atoms, atomMap));
};
SGroup.clone = function (sgroup, aidMap) {
var cp = new SGroup(sgroup.type);
for (var field in sgroup.data) // TODO: remove all non-primitive properties from 'data'
cp.data[field] = sgroup.data[field];
cp.atoms = sgroup.atoms.map(function (elem) {
return aidMap[elem];
});
cp.pp = sgroup.pp;
cp.bracketBox = sgroup.bracketBox;
cp.patoms = null;
cp.bonds = null;
cp.allAtoms = sgroup.allAtoms;
return cp;
};
SGroup.addAtom = function (sgroup, aid) {
sgroup.atoms.push(aid);
};
SGroup.removeAtom = function (sgroup, aid) {
for (var i = 0; i < sgroup.atoms.length; ++i) {
if (sgroup.atoms[i] === aid) {
sgroup.atoms.splice(i, 1);
return;
}
}
console.error('The atom is not found in the given s-group');
};
SGroup.getCrossBonds = function (inBonds, xBonds, mol, parentAtomSet) {
mol.bonds.each(function (bid, bond) {
if (Set.contains(parentAtomSet, bond.begin) && Set.contains(parentAtomSet, bond.end)) {
if (inBonds != null)
inBonds.push(bid);
} else if (Set.contains(parentAtomSet, bond.begin) || Set.contains(parentAtomSet, bond.end)) {
if (xBonds != null)
xBonds.push(bid);
}
}, this);
};
SGroup.bracketPos = function (sg, mol, xbonds) { // eslint-disable-line max-statements
var atoms = sg.atoms;
if (!xbonds || xbonds.length !== 2) {
sg.bracketDir = new Vec2(1, 0);
} else {
var p1 = mol.bonds.get(xbonds[0]).getCenter(mol);
var p2 = mol.bonds.get(xbonds[1]).getCenter(mol);
sg.bracketDir = Vec2.diff(p2, p1).normalized();
}
var d = sg.bracketDir;
var bb = null;
var contentBoxes = [];
atoms.forEach(function (aid) {
var atom = mol.atoms.get(aid);
var pos = new Vec2(atom.pp);
var ext = new Vec2(0.05 * 3, 0.05 * 3);
var bba = new Box2Abs(pos, pos).extend(ext, ext);
contentBoxes.push(bba);
}, this);
contentBoxes.forEach(function (bba) {
var bbb = null;
[bba.p0.x, bba.p1.x].forEach(function (x) {
[bba.p0.y, bba.p1.y].forEach(function (y) {
var v = new Vec2(x, y);
var p = new Vec2(Vec2.dot(v, d), Vec2.dot(v, d.rotateSC(1, 0)));
bbb = (bbb === null) ? new Box2Abs(p, p) : bbb.include(p);
}, this);
}, this);
bb = (bb === null) ? bbb : Box2Abs.union(bb, bbb);
}, this);
var vext = new Vec2(0.2, 0.4);
if (bb !== null) bb = bb.extend(vext, vext);
sg.bracketBox = bb;
};
SGroup.getBracketParameters = function (mol, xbonds, atomSet, bb, d, n) { // eslint-disable-line max-params
function BracketParams(c, d, w, h) {
this.c = c;
this.d = d;
this.n = d.rotateSC(1, 0);
this.w = w;
this.h = h;
}
var brackets = [];
if (xbonds.length < 2) {
(function () {
d = d || new Vec2(1, 0);
n = n || d.rotateSC(1, 0);
var bracketWidth = Math.min(0.25, bb.sz().x * 0.3);
var cl = Vec2.lc2(d, bb.p0.x, n, 0.5 * (bb.p0.y + bb.p1.y));
var cr = Vec2.lc2(d, bb.p1.x, n, 0.5 * (bb.p0.y + bb.p1.y));
var bracketHeight = bb.sz().y;
brackets.push(new BracketParams(cl, d.negated(), bracketWidth, bracketHeight), new BracketParams(cr, d, bracketWidth, bracketHeight));
})();
} else if (xbonds.length === 2) {
(function () { // eslint-disable-line max-statements
var b1 = mol.bonds.get(xbonds[0]);
var b2 = mol.bonds.get(xbonds[1]);
var cl0 = b1.getCenter(mol);
var cr0 = b2.getCenter(mol);
var dr = Vec2.diff(cr0, cl0).normalized();
var dl = dr.negated();
var bracketWidth = 0.25;
var bracketHeight = 1.5;
brackets.push(new BracketParams(cl0.addScaled(dl, 0), dl, bracketWidth, bracketHeight),
new BracketParams(cr0.addScaled(dr, 0), dr, bracketWidth, bracketHeight));
})();
} else {
(function () {
for (var i = 0; i < xbonds.length; ++i) {
var b = mol.bonds.get(xbonds[i]);
var c = b.getCenter(mol);
var d = Set.contains(atomSet, b.begin) ? b.getDir(mol) : b.getDir(mol).negated();
brackets.push(new BracketParams(c, d, 0.2, 1.0));
}
})();
}
return brackets;
};
SGroup.getObjBBox = function (atoms, mol) {
console.assert(atoms.length != 0, 'Atom list is empty');
var a0 = mol.atoms.get(atoms[0]).pp;
var bb = new Box2Abs(a0, a0);
for (var i = 1; i < atoms.length; ++i) {
var aid = atoms[i];
var atom = mol.atoms.get(aid);
var p = atom.pp;
bb = bb.include(p);
}
return bb;
};
SGroup.getAtoms = function (mol, sg) {
/* shoud we use prototype? */
if (!sg.allAtoms)
return sg.atoms;
var atoms = [];
mol.atoms.each(function (aid) {
atoms.push(aid);
});
return atoms;
};
SGroup.getBonds = function (mol, sg) {
var atoms = SGroup.getAtoms(mol, sg);
var bonds = [];
mol.bonds.each(function (bid, bond) {
if (atoms.indexOf(bond.begin) >= 0 && atoms.indexOf(bond.end) >= 0) bonds.push(bid);
});
return bonds;
};
SGroup.prepareMulForSaving = function (sgroup, mol) { // eslint-disable-line max-statements
sgroup.atoms.sort((a, b) => a - b);
sgroup.atomSet = Set.fromList(sgroup.atoms);
sgroup.parentAtomSet = Set.clone(sgroup.atomSet);
var inBonds = [];
var xBonds = [];
mol.bonds.each(function (bid, bond) {
if (Set.contains(sgroup.parentAtomSet, bond.begin) && Set.contains(sgroup.parentAtomSet, bond.end))
inBonds.push(bid);
else if (Set.contains(sgroup.parentAtomSet, bond.begin) || Set.contains(sgroup.parentAtomSet, bond.end))
xBonds.push(bid);
}, sgroup);
if (xBonds.length !== 0 && xBonds.length !== 2) {
throw {
'id': sgroup.id,
'error-type': 'cross-bond-number',
'message': 'Unsupported cross-bonds number'
};
}
var xAtom1 = -1;
var xAtom2 = -1;
var crossBond = null;
if (xBonds.length === 2) {
var bond1 = mol.bonds.get(xBonds[0]);
xAtom1 = Set.contains(sgroup.parentAtomSet, bond1.begin) ? bond1.begin : bond1.end;
var bond2 = mol.bonds.get(xBonds[1]);
xAtom2 = Set.contains(sgroup.parentAtomSet, bond2.begin) ? bond2.begin : bond2.end;
crossBond = bond2;
}
var amap = null;
var tailAtom = xAtom2;
var newAtoms = [];
for (var j = 0; j < sgroup.data.mul - 1; j++) {
amap = {};
sgroup.atoms.forEach(function (aid) {
var atom = mol.atoms.get(aid);
var aid2 = mol.atoms.add(new Atom(atom));
newAtoms.push(aid2);
sgroup.atomSet[aid2] = 1;
amap[aid] = aid2;
});
inBonds.forEach(function (bid) {
var bond = mol.bonds.get(bid);
var newBond = new Bond(bond);
newBond.begin = amap[newBond.begin];
newBond.end = amap[newBond.end];
mol.bonds.add(newBond);
});
if (crossBond !== null) {
var newCrossBond = new Bond(crossBond);
newCrossBond.begin = tailAtom;
newCrossBond.end = amap[xAtom1];
mol.bonds.add(newCrossBond);
tailAtom = amap[xAtom2];
}
}
if (tailAtom >= 0) {
var xBond2 = mol.bonds.get(xBonds[1]);
if (xBond2.begin === xAtom2)
xBond2.begin = tailAtom;
else
xBond2.end = tailAtom;
}
sgroup.bonds = xBonds;
newAtoms.forEach(function (aid) {
mol.sGroupForest.getPathToRoot(sgroup.id).reverse().forEach(function (sgid) {
mol.atomAddToSGroup(sgid, aid);
});
});
};
SGroup.getMassCentre = function (mol, atoms) {
var c = new Vec2(); // mass centre
for (var i = 0; i < atoms.length; ++i)
c = c.addScaled(mol.atoms.get(atoms[i]).pp, 1.0 / atoms.length);
return c;
};
module.exports = SGroup;