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

981 lines
28 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 Set = require('../util/set');
var scale = require('../util/scale');
var Struct = require('../chem/struct');
var ReStruct = require('../render/restruct');
var DEBUG = { debug: false, logcnt: 0, logmouse: false, hl: false };
DEBUG.logMethod = function () { };
function Base() {
this.type = 'OpBase';
// assert here?
this.execute = function () {
throw new Error('Operation.execute() is not implemented');
};
this.invert = function () {
throw new Error('Operation.invert() is not implemented');
};
this.perform = function (restruct) {
this.execute(restruct);
/* eslint-disable no-underscore-dangle */
if (!this._inverted) {
this._inverted = this.invert();
this._inverted._inverted = this;
}
return this._inverted;
};
this.isDummy = function (restruct) {
return this._isDummy ? this._isDummy(restruct) : false;
/* eslint-enable no-underscore-dangle */
};
}
function AtomAdd(atom, pos) {
this.data = { aid: null, atom: atom, pos: pos };
this.execute = function (restruct) {
var struct = restruct.molecule;
var pp = {};
if (this.data.atom) {
for (var p in this.data.atom)
if (this.data.atom.hasOwnProperty(p)) pp[p] = this.data.atom[p];
}
pp.label = pp.label || 'C';
if (!(typeof this.data.aid === "number"))
this.data.aid = struct.atoms.add(new Struct.Atom(pp));
else
struct.atoms.set(this.data.aid, new Struct.Atom(pp));
// notifyAtomAdded
var atomData = new ReStruct.Atom(restruct.molecule.atoms.get(this.data.aid));
atomData.component = restruct.connectedComponents.add(Set.single(this.data.aid));
restruct.atoms.set(this.data.aid, atomData);
restruct.markAtom(this.data.aid, 1);
struct.atomSetPos(this.data.aid, new Vec2(this.data.pos));
};
this.invert = function () {
var ret = new AtomDelete();
ret.data = this.data;
return ret;
};
}
AtomAdd.prototype = new Base();
function AtomDelete(aid) {
this.data = { aid: aid, atom: null, pos: null };
this.execute = function (restruct) {
var struct = restruct.molecule;
if (!this.data.atom) {
this.data.atom = struct.atoms.get(this.data.aid);
this.data.pos = this.data.atom.pp;
}
// notifyAtomRemoved(this.data.aid);
var atom = restruct.atoms.get(this.data.aid);
var set = restruct.connectedComponents.get(atom.component);
Set.remove(set, this.data.aid);
if (Set.size(set) == 0)
restruct.connectedComponents.remove(atom.component);
restruct.clearVisel(atom.visel);
restruct.atoms.unset(this.data.aid);
restruct.markItemRemoved();
struct.atoms.remove(this.data.aid);
};
this.invert = function () {
var ret = new AtomAdd();
ret.data = this.data;
return ret;
};
}
AtomDelete.prototype = new Base();
function AtomAttr(aid, attribute, value) {
this.data = { aid: aid, attribute: attribute, value: value };
this.data2 = null;
this.execute = function (restruct) {
var atom = restruct.molecule.atoms.get(this.data.aid);
if (!this.data2)
this.data2 = { aid: this.data.aid, attribute: this.data.attribute, value: atom[this.data.attribute] };
atom[this.data.attribute] = this.data.value;
invalidateAtom(restruct, this.data.aid);
};
this._isDummy = function (restruct) { // eslint-disable-line no-underscore-dangle
return restruct.molecule.atoms.get(this.data.aid)[this.data.attribute] == this.data.value;
};
this.invert = function () {
var ret = new AtomAttr();
ret.data = this.data2;
ret.data2 = this.data;
return ret;
};
}
AtomAttr.prototype = new Base();
function AtomMove(aid, d, noinvalidate) {
this.data = { aid: aid, d: d, noinvalidate: noinvalidate };
this.execute = function (restruct) {
var struct = restruct.molecule;
var aid = this.data.aid;
var d = this.data.d;
struct.atoms.get(aid).pp.add_(d); // eslint-disable-line no-underscore-dangle
restruct.atoms.get(aid).visel.translate(scale.obj2scaled(d, restruct.render.options));
this.data.d = d.negated();
if (!this.data.noinvalidate)
invalidateAtom(restruct, aid, 1);
};
this._isDummy = function () { // eslint-disable-line no-underscore-dangle
return this.data.d.x == 0 && this.data.d.y == 0;
};
this.invert = function () {
var ret = new AtomMove();
ret.data = this.data;
return ret;
};
}
AtomMove.prototype = new Base();
function BondMove(bid, d) {
this.data = { bid: bid, d: d };
this.execute = function (restruct) {
restruct.bonds.get(this.data.bid).visel.translate(scale.obj2scaled(this.data.d, restruct.render.options));
this.data.d = this.data.d.negated();
};
this.invert = function () {
var ret = new BondMove();
ret.data = this.data;
return ret;
};
}
BondMove.prototype = new Base();
function LoopMove(id, d) {
this.data = { id: id, d: d };
this.execute = function (restruct) {
// not sure if there should be an action to move a loop in the first place
// but we have to somehow move the aromatic ring, which is associated with the loop, rather than with any of the bonds
if (restruct.reloops.get(this.data.id) && restruct.reloops.get(this.data.id).visel)
restruct.reloops.get(this.data.id).visel.translate(scale.obj2scaled(this.data.d, restruct.render.options));
this.data.d = this.data.d.negated();
};
this.invert = function () {
var ret = new LoopMove();
ret.data = this.data;
return ret;
};
}
LoopMove.prototype = new Base();
function SGroupAtomAdd(sgid, aid) {
this.type = 'OpSGroupAtomAdd';
this.data = { sgid, aid };
this.execute = function (restruct) {
const struct = restruct.molecule;
const aid = this.data.aid;
const sgid = this.data.sgid;
const atom = struct.atoms.get(aid);
const sg = struct.sgroups.get(sgid);
if (sg.atoms.indexOf(aid) >= 0)
throw new Error('The same atom cannot be added to an S-group more than once');
if (!atom)
throw new Error('OpSGroupAtomAdd: Atom ' + aid + ' not found');
struct.atomAddToSGroup(sgid, aid);
invalidateAtom(restruct, aid);
};
this.invert = function () {
const ret = new SGroupAtomRemove();
ret.data = this.data;
return ret;
};
}
SGroupAtomAdd.prototype = new Base();
function SGroupAtomRemove(sgid, aid) {
this.type = 'OpSGroupAtomRemove';
this.data = { sgid, aid };
this.execute = function (restruct) {
const aid = this.data.aid;
const sgid = this.data.sgid;
const struct = restruct.molecule;
const atom = struct.atoms.get(aid);
const sg = struct.sgroups.get(sgid);
Struct.SGroup.removeAtom(sg, aid);
Set.remove(atom.sgs, sgid);
invalidateAtom(restruct, aid);
};
this.invert = function () {
const ret = new SGroupAtomAdd();
ret.data = this.data;
return ret;
};
}
SGroupAtomRemove.prototype = new Base();
function SGroupAttr(sgid, attr, value) {
this.type = 'OpSGroupAttr';
this.data = { sgid, attr, value };
this.execute = function (restruct) {
const struct = restruct.molecule;
const sgid = this.data.sgid;
const sg = struct.sgroups.get(sgid);
if (sg.type === 'DAT' && restruct.sgroupData.has(sgid)) {
// clean the stuff here, else it might be left behind if the sgroups is set to "attached"
restruct.clearVisel(restruct.sgroupData.get(sgid).visel);
restruct.sgroupData.unset(sgid);
}
this.data.value = sg.setAttr(this.data.attr, this.data.value);
};
this.invert = function () {
const ret = new SGroupAttr();
ret.data = this.data;
return ret;
};
}
SGroupAttr.prototype = new Base();
function SGroupCreate(sgid, type, pp) {
this.type = 'OpSGroupCreate';
this.data = { sgid, type, pp };
this.execute = function (restruct) {
const struct = restruct.molecule;
const sg = new Struct.SGroup(this.data.type);
const sgid = this.data.sgid;
sg.id = sgid;
struct.sgroups.set(sgid, sg);
if (this.data.pp)
struct.sgroups.get(sgid).pp = new Vec2(this.data.pp);
restruct.sgroups.set(sgid, new ReStruct.SGroup(struct.sgroups.get(sgid)));
this.data.sgid = sgid;
};
this.invert = function () {
const ret = new SGroupDelete();
ret.data = this.data;
return ret;
};
}
SGroupCreate.prototype = new Base();
function SGroupDelete(sgid) {
this.type = 'OpSGroupDelete';
this.data = { sgid };
this.execute = function (restruct) {
const struct = restruct.molecule;
const sgid = this.data.sgid;
const sg = restruct.sgroups.get(sgid);
this.data.type = sg.item.type;
this.data.pp = sg.item.pp;
if (sg.item.type === 'DAT' && restruct.sgroupData.has(sgid)) {
restruct.clearVisel(restruct.sgroupData.get(sgid).visel);
restruct.sgroupData.unset(sgid);
}
restruct.clearVisel(sg.visel);
if (sg.item.atoms.length !== 0)
throw new Error('S-Group not empty!');
restruct.sgroups.unset(sgid);
struct.sgroups.remove(sgid);
};
this.invert = function () {
const ret = new SGroupCreate();
ret.data = this.data;
return ret;
};
}
SGroupDelete.prototype = new Base();
function SGroupAddToHierarchy(sgid, parent, children) {
this.type = 'OpSGroupAddToHierarchy';
this.data = { sgid, parent, children };
this.execute = function (restruct) {
const struct = restruct.molecule;
const sgid = this.data.sgid;
const relations = struct.sGroupForest.insert(sgid, parent, children);
this.data.parent = relations.parent;
this.data.children = relations.children;
};
this.invert = function () {
const ret = new SGroupRemoveFromHierarchy();
ret.data = this.data;
return ret;
};
}
SGroupAddToHierarchy.prototype = new Base();
function SGroupRemoveFromHierarchy(sgid) {
this.type = 'OpSGroupRemoveFromHierarchy';
this.data = { sgid };
this.execute = function (restruct) {
const struct = restruct.molecule;
const sgid = this.data.sgid;
this.data.parent = struct.sGroupForest.parent.get(sgid);
this.data.children = struct.sGroupForest.children.get(sgid);
struct.sGroupForest.remove(sgid);
};
this.invert = function () {
const ret = new SGroupAddToHierarchy();
ret.data = this.data;
return ret;
};
}
SGroupRemoveFromHierarchy.prototype = new Base();
function BondAdd(begin, end, bond) {
this.data = { bid: null, bond: bond, begin: begin, end: end };
this.execute = function (restruct) { // eslint-disable-line max-statements
var struct = restruct.molecule;
if (this.data.begin == this.data.end)
throw new Error('Distinct atoms expected');
if (DEBUG.debug && this.molecule.checkBondExists(this.data.begin, this.data.end))
throw new Error('Bond already exists');
invalidateAtom(restruct, this.data.begin, 1);
invalidateAtom(restruct, this.data.end, 1);
var pp = {};
if (this.data.bond) {
for (var p in this.data.bond)
if (this.data.bond.hasOwnProperty(p)) pp[p] = this.data.bond[p];
}
pp.type = pp.type || Struct.Bond.PATTERN.TYPE.SINGLE;
pp.begin = this.data.begin;
pp.end = this.data.end;
if (!(typeof this.data.bid === "number"))
this.data.bid = struct.bonds.add(new Struct.Bond(pp));
else
struct.bonds.set(this.data.bid, new Struct.Bond(pp));
struct.bondInitHalfBonds(this.data.bid);
struct.atomAddNeighbor(struct.bonds.get(this.data.bid).hb1);
struct.atomAddNeighbor(struct.bonds.get(this.data.bid).hb2);
// notifyBondAdded
restruct.bonds.set(this.data.bid, new ReStruct.Bond(restruct.molecule.bonds.get(this.data.bid)));
restruct.markBond(this.data.bid, 1);
};
this.invert = function () {
var ret = new BondDelete();
ret.data = this.data;
return ret;
};
}
BondAdd.prototype = new Base();
function BondDelete(bid) {
this.data = { bid: bid, bond: null, begin: null, end: null };
this.execute = function (restruct) { // eslint-disable-line max-statements
var struct = restruct.molecule;
if (!this.data.bond) {
this.data.bond = struct.bonds.get(this.data.bid);
this.data.begin = this.data.bond.begin;
this.data.end = this.data.bond.end;
}
invalidateBond(restruct, this.data.bid);
// notifyBondRemoved
var rebond = restruct.bonds.get(this.data.bid);
[rebond.b.hb1, rebond.b.hb2].forEach(function (hbid) {
var hb = restruct.molecule.halfBonds.get(hbid);
if (hb.loop >= 0)
restruct.loopRemove(hb.loop);
}, restruct);
restruct.clearVisel(rebond.visel);
restruct.bonds.unset(this.data.bid);
restruct.markItemRemoved();
var bond = struct.bonds.get(this.data.bid);
[bond.hb1, bond.hb2].forEach(function (hbid) {
var hb = struct.halfBonds.get(hbid);
var atom = struct.atoms.get(hb.begin);
var pos = atom.neighbors.indexOf(hbid);
var prev = (pos + atom.neighbors.length - 1) % atom.neighbors.length;
var next = (pos + 1) % atom.neighbors.length;
struct.setHbNext(atom.neighbors[prev], atom.neighbors[next]);
atom.neighbors.splice(pos, 1);
}, this);
struct.halfBonds.unset(bond.hb1);
struct.halfBonds.unset(bond.hb2);
struct.bonds.remove(this.data.bid);
};
this.invert = function () {
var ret = new BondAdd();
ret.data = this.data;
return ret;
};
}
BondDelete.prototype = new Base();
function BondAttr(bid, attribute, value) {
this.data = { bid: bid, attribute: attribute, value: value };
this.data2 = null;
this.execute = function (restruct) {
var bond = restruct.molecule.bonds.get(this.data.bid);
if (!this.data2)
this.data2 = { bid: this.data.bid, attribute: this.data.attribute, value: bond[this.data.attribute] };
bond[this.data.attribute] = this.data.value;
invalidateBond(restruct, this.data.bid);
if (this.data.attribute === 'type')
invalidateLoop(restruct, this.data.bid);
};
this._isDummy = function (restruct) { // eslint-disable-line no-underscore-dangle
return restruct.molecule.bonds.get(this.data.bid)[this.data.attribute] == this.data.value;
};
this.invert = function () {
var ret = new BondAttr();
ret.data = this.data2;
ret.data2 = this.data;
return ret;
};
}
BondAttr.prototype = new Base();
function FragmentAdd(frid) {
this.frid = (typeof frid === 'undefined') ? null : frid;
this.execute = function (restruct) {
var struct = restruct.molecule;
var frag = {};
if (this.frid == null)
this.frid = struct.frags.add(frag);
else
struct.frags.set(this.frid, frag);
restruct.frags.set(this.frid, new ReStruct.Frag(frag)); // TODO add ReStruct.notifyFragmentAdded
};
this.invert = function () {
return new FragmentDelete(this.frid);
};
}
FragmentAdd.prototype = new Base();
function FragmentDelete(frid) {
this.frid = frid;
this.execute = function (restruct) {
var struct = restruct.molecule;
invalidateItem(restruct, 'frags', this.frid, 1);
restruct.frags.unset(this.frid);
struct.frags.remove(this.frid); // TODO add ReStruct.notifyFragmentRemoved
};
this.invert = function () {
return new FragmentAdd(this.frid);
};
}
FragmentDelete.prototype = new Base();
function RGroupAttr(rgid, attribute, value) {
this.data = { rgid: rgid, attribute: attribute, value: value };
this.data2 = null;
this.execute = function (restruct) {
var rgp = restruct.molecule.rgroups.get(this.data.rgid);
if (!this.data2)
this.data2 = { rgid: this.data.rgid, attribute: this.data.attribute, value: rgp[this.data.attribute] };
rgp[this.data.attribute] = this.data.value;
invalidateItem(restruct, 'rgroups', this.data.rgid);
};
this._isDummy = function (restruct) { // eslint-disable-line no-underscore-dangle
return restruct.molecule.rgroups.get(this.data.rgid)[this.data.attribute] == this.data.value;
};
this.invert = function () {
var ret = new RGroupAttr();
ret.data = this.data2;
ret.data2 = this.data;
return ret;
};
}
RGroupAttr.prototype = new Base();
function RGroupFragment(rgid, frid, rg) {
this.type = 'OpAddOrDeleteRGFragment';
this.rgid_new = rgid;
this.rg_new = rg;
this.rgid_old = null;
this.rg_old = null;
this.frid = frid;
this.execute = function (restruct) { // eslint-disable-line max-statements
const struct = restruct.molecule;
this.rgid_old = this.rgid_old || Struct.RGroup.findRGroupByFragment(struct.rgroups, this.frid);
this.rg_old = (this.rgid_old ? struct.rgroups.get(this.rgid_old) : null);
if (this.rg_old) {
this.rg_old.frags.remove(this.rg_old.frags.keyOf(this.frid));
restruct.clearVisel(restruct.rgroups.get(this.rgid_old).visel);
if (this.rg_old.frags.count() === 0) {
restruct.rgroups.unset(this.rgid_old);
struct.rgroups.unset(this.rgid_old);
restruct.markItemRemoved();
} else {
restruct.markItem('rgroups', this.rgid_old, 1);
}
}
if (this.rgid_new) {
let rgNew = struct.rgroups.get(this.rgid_new);
if (!rgNew) {
rgNew = this.rg_new || new Struct.RGroup();
struct.rgroups.set(this.rgid_new, rgNew);
restruct.rgroups.set(this.rgid_new, new ReStruct.RGroup(rgNew));
} else {
restruct.markItem('rgroups', this.rgid_new, 1);
}
rgNew.frags.add(this.frid);
}
};
this.invert = function () {
return new RGroupFragment(this.rgid_old, this.frid, this.rg_old);
};
}
RGroupFragment.prototype = new Base();
function UpdateIfThen(rgNew, rgOld) {
this.type = 'OpUpdateIfThenValues';
this.rgid_new = rgNew;
this.rgid_old = rgOld;
this.ifThenHistory = {};
this.execute = function (restruct) {
const struct = restruct.molecule;
struct.rgroups.keys().forEach(rgKey => {
const rgValue = struct.rgroups.get(rgKey);
if (rgValue.ifthen === this.rgid_old) {
rgValue.ifthen = this.rgid_new;
this.ifThenHistory[rgKey] = this.rgid_old;
struct.rgroups.set(rgKey, rgValue);
}
});
};
this.invert = function () {
return new RestoreIfThen(this.rgid_new, this.rgid_old, this.ifThenHistory);
};
}
UpdateIfThen.prototype = new Base();
function RestoreIfThen(rgNew, rgOld, history) {
this.type = 'OpRestoreIfThenValues';
this.rgid_new = rgNew;
this.rgid_old = rgOld;
this.ifThenHistory = history || {};
this.execute = function (restruct) {
const struct = restruct.molecule;
Object.keys(this.ifThenHistory).forEach(rgid => {
const rgValue = struct.rgroups.get(rgid);
rgValue.ifthen = this.ifThenHistory[rgid];
struct.rgroups.set(rgid, rgValue);
});
};
this.invert = function () {
return new UpdateIfThen(this.rgid_old, this.rgid_new);
};
}
RestoreIfThen.prototype = new Base();
function RxnArrowAdd(pos) {
this.data = { arid: null, pos: pos };
this.execute = function (restruct) {
var struct = restruct.molecule;
if (!(typeof this.data.arid === 'number'))
this.data.arid = struct.rxnArrows.add(new Struct.RxnArrow());
else
struct.rxnArrows.set(this.data.arid, new Struct.RxnArrow());
// notifyRxnArrowAdded
restruct.rxnArrows.set(this.data.arid, new ReStruct.RxnArrow(restruct.molecule.rxnArrows.get(this.data.arid)));
struct.rxnArrowSetPos(this.data.arid, new Vec2(this.data.pos));
invalidateItem(restruct, 'rxnArrows', this.data.arid, 1);
};
this.invert = function () {
var ret = new RxnArrowDelete();
ret.data = this.data;
return ret;
};
}
RxnArrowAdd.prototype = new Base();
function RxnArrowDelete(arid) {
this.data = { arid: arid, pos: null };
this.execute = function (restruct) {
var struct = restruct.molecule;
if (!this.data.pos)
this.data.pos = struct.rxnArrows.get(this.data.arid).pp;
// notifyRxnArrowRemoved
restruct.markItemRemoved();
restruct.clearVisel(restruct.rxnArrows.get(this.data.arid).visel);
restruct.rxnArrows.unset(this.data.arid);
struct.rxnArrows.remove(this.data.arid);
};
this.invert = function () {
var ret = new RxnArrowAdd();
ret.data = this.data;
return ret;
};
}
RxnArrowDelete.prototype = new Base();
function RxnArrowMove(id, d, noinvalidate) {
this.data = { id: id, d: d, noinvalidate: noinvalidate };
this.execute = function (restruct) {
var struct = restruct.molecule;
var id = this.data.id;
var d = this.data.d;
struct.rxnArrows.get(id).pp.add_(d); // eslint-disable-line no-underscore-dangle
restruct.rxnArrows.get(id).visel.translate(scale.obj2scaled(d, restruct.render.options));
this.data.d = d.negated();
if (!this.data.noinvalidate)
invalidateItem(restruct, 'rxnArrows', id, 1);
};
this.invert = function () {
var ret = new RxnArrowMove();
ret.data = this.data;
return ret;
};
}
RxnArrowMove.prototype = new Base();
function RxnPlusAdd(pos) {
this.data = { plid: null, pos: pos };
this.execute = function (restruct) {
var struct = restruct.molecule;
if (!(typeof this.data.plid === 'number'))
this.data.plid = struct.rxnPluses.add(new Struct.RxnPlus());
else
struct.rxnPluses.set(this.data.plid, new Struct.RxnPlus());
// notifyRxnPlusAdded
restruct.rxnPluses.set(this.data.plid, new ReStruct.RxnPlus(restruct.molecule.rxnPluses.get(this.data.plid)));
struct.rxnPlusSetPos(this.data.plid, new Vec2(this.data.pos));
invalidateItem(restruct, 'rxnPluses', this.data.plid, 1);
};
this.invert = function () {
var ret = new RxnPlusDelete();
ret.data = this.data;
return ret;
};
}
RxnPlusAdd.prototype = new Base();
function RxnPlusDelete(plid) {
this.data = { plid: plid, pos: null };
this.execute = function (restruct) {
var struct = restruct.molecule;
if (!this.data.pos)
this.data.pos = struct.rxnPluses.get(this.data.plid).pp;
// notifyRxnPlusRemoved
restruct.markItemRemoved();
restruct.clearVisel(restruct.rxnPluses.get(this.data.plid).visel);
restruct.rxnPluses.unset(this.data.plid);
struct.rxnPluses.remove(this.data.plid);
};
this.invert = function () {
var ret = new RxnPlusAdd();
ret.data = this.data;
return ret;
};
}
RxnPlusDelete.prototype = new Base();
function RxnPlusMove(id, d, noinvalidate) {
this.data = { id: id, d: d, noinvalidate: noinvalidate };
this.execute = function (restruct) {
var struct = restruct.molecule;
var id = this.data.id;
var d = this.data.d;
struct.rxnPluses.get(id).pp.add_(d); // eslint-disable-line no-underscore-dangle
restruct.rxnPluses.get(id).visel.translate(scale.obj2scaled(d, restruct.render.options));
this.data.d = d.negated();
if (!this.data.noinvalidate)
invalidateItem(restruct, 'rxnPluses', id, 1);
};
this.invert = function () {
var ret = new RxnPlusMove();
ret.data = this.data;
return ret;
};
}
RxnPlusMove.prototype = new Base();
function SGroupDataMove(id, d) {
this.data = { id: id, d: d };
this.execute = function (restruct) {
var struct = restruct.molecule;
struct.sgroups.get(this.data.id).pp.add_(this.data.d); // eslint-disable-line no-underscore-dangle
this.data.d = this.data.d.negated();
invalidateItem(restruct, 'sgroupData', this.data.id, 1); // [MK] this currently does nothing since the DataSGroupData Visel only contains the highlighting/selection and SGroups are redrawn every time anyway
};
this.invert = function () {
var ret = new SGroupDataMove();
ret.data = this.data;
return ret;
};
}
SGroupDataMove.prototype = new Base();
function CanvasLoad(struct) {
this.data = { struct: struct };
this.execute = function (restruct) {
var oldStruct = restruct.molecule;
restruct.clearVisels(); // TODO: What is it?
restruct.render.setMolecule(this.data.struct);
this.data.struct = oldStruct;
};
this.invert = function () {
var ret = new CanvasLoad();
ret.data = this.data;
return ret;
};
}
CanvasLoad.prototype = new Base();
function ChiralFlagAdd(pos) {
this.data = { pos: pos };
this.execute = function (restruct) {
var struct = restruct.molecule;
if (restruct.chiralFlags.count() > 0) {
// throw new Error('Cannot add more than one Chiral flag');
restruct.clearVisel(restruct.chiralFlags.get(0).visel);
restruct.chiralFlags.unset(0);
}
restruct.chiralFlags.set(0, new ReStruct.ChiralFlag(pos));
struct.isChiral = true;
invalidateItem(restruct, 'chiralFlags', 0, 1);
};
this.invert = function () {
var ret = new ChiralFlagDelete();
ret.data = this.data;
return ret;
};
}
ChiralFlagAdd.prototype = new Base();
function ChiralFlagDelete() {
this.data = { pos: null };
this.execute = function (restruct) {
var struct = restruct.molecule;
if (restruct.chiralFlags.count() < 1)
throw new Error('Cannot remove chiral flag');
restruct.clearVisel(restruct.chiralFlags.get(0).visel);
this.data.pos = restruct.chiralFlags.get(0).pp;
restruct.chiralFlags.unset(0);
struct.isChiral = false;
};
this.invert = function () {
var ret = new ChiralFlagAdd(this.data.pos);
ret.data = this.data;
return ret;
};
}
ChiralFlagDelete.prototype = new Base();
function ChiralFlagMove(d) {
this.data = { d: d };
this.execute = function (restruct) {
restruct.chiralFlags.get(0).pp.add_(this.data.d); // eslint-disable-line no-underscore-dangle
this.data.d = this.data.d.negated();
invalidateItem(restruct, 'chiralFlags', 0, 1);
};
this.invert = function () {
var ret = new ChiralFlagMove();
ret.data = this.data;
return ret;
};
}
ChiralFlagMove.prototype = new Base();
function AlignDescriptors() {
this.type = 'OpAlignDescriptors';
this.history = {};
this.execute = function (restruct) {
const sgroups = restruct.molecule.sgroups.values().reverse();
let alignPoint = sgroups.reduce(
(acc, sg) => new Vec2(
Math.max(sg.bracketBox.p1.x, acc.x),
Math.min(sg.bracketBox.p0.y, acc.y)
), new Vec2(0.0, Infinity)
)
.add(new Vec2(0.5, -0.5));
sgroups.forEach(sg => {
this.history[sg.id] = sg.pp;
alignPoint = alignPoint.add(new Vec2(0.0, 0.5));
sg.pp = alignPoint;
restruct.molecule.sgroups.set(sg.id, sg);
});
};
this.invert = function () {
return new RestoreDescriptorsPosition(this.history);
};
}
AlignDescriptors.prototype = new Base();
function RestoreDescriptorsPosition(history) {
this.type = 'OpRestoreDescriptorsPosition';
this.history = history;
this.execute = function (restruct) {
const sgroups = restruct.molecule.sgroups.values();
sgroups.forEach(sg => {
sg.pp = this.history[sg.id];
restruct.molecule.sgroups.set(sg.id, sg);
});
};
this.invert = function () {
return new AlignDescriptors();
};
}
RestoreDescriptorsPosition.prototype = new Base();
function invalidateAtom(restruct, aid, level) {
var atom = restruct.atoms.get(aid);
restruct.markAtom(aid, level ? 1 : 0);
var hbs = restruct.molecule.halfBonds;
for (var i = 0; i < atom.a.neighbors.length; ++i) {
var hbid = atom.a.neighbors[i];
if (hbs.has(hbid)) {
var hb = hbs.get(hbid);
restruct.markBond(hb.bid, 1);
restruct.markAtom(hb.end, 0);
if (level)
invalidateLoop(restruct, hb.bid);
}
}
}
function invalidateLoop(restruct, bid) {
var bond = restruct.bonds.get(bid);
var lid1 = restruct.molecule.halfBonds.get(bond.b.hb1).loop;
var lid2 = restruct.molecule.halfBonds.get(bond.b.hb2).loop;
if (lid1 >= 0)
restruct.loopRemove(lid1);
if (lid2 >= 0)
restruct.loopRemove(lid2);
}
function invalidateBond(restruct, bid) {
var bond = restruct.bonds.get(bid);
invalidateLoop(restruct, bid);
invalidateAtom(restruct, bond.b.begin, 0);
invalidateAtom(restruct, bond.b.end, 0);
}
function invalidateItem(restruct, map, id, level) {
if (map === 'atoms') {
invalidateAtom(restruct, id, level);
} else if (map === 'bonds') {
invalidateBond(restruct, id);
if (level > 0)
invalidateLoop(restruct, id);
} else {
restruct.markItem(map, id, level);
}
}
module.exports = {
AtomAdd: AtomAdd,
AtomDelete: AtomDelete,
AtomAttr: AtomAttr,
AtomMove: AtomMove,
BondMove: BondMove,
LoopMove: LoopMove,
SGroupAtomAdd: SGroupAtomAdd,
SGroupAtomRemove: SGroupAtomRemove,
SGroupAttr: SGroupAttr,
SGroupCreate: SGroupCreate,
SGroupDelete: SGroupDelete,
SGroupAddToHierarchy: SGroupAddToHierarchy,
SGroupRemoveFromHierarchy: SGroupRemoveFromHierarchy,
BondAdd: BondAdd,
BondDelete: BondDelete,
BondAttr: BondAttr,
FragmentAdd: FragmentAdd,
FragmentDelete: FragmentDelete,
RGroupAttr: RGroupAttr,
RGroupFragment: RGroupFragment,
RxnArrowAdd: RxnArrowAdd,
RxnArrowDelete: RxnArrowDelete,
RxnArrowMove: RxnArrowMove,
RxnPlusAdd: RxnPlusAdd,
RxnPlusDelete: RxnPlusDelete,
RxnPlusMove: RxnPlusMove,
SGroupDataMove: SGroupDataMove,
CanvasLoad: CanvasLoad,
ChiralFlagAdd: ChiralFlagAdd,
ChiralFlagDelete: ChiralFlagDelete,
ChiralFlagMove: ChiralFlagMove,
UpdateIfThen: UpdateIfThen,
AlignDescriptors: AlignDescriptors,
RestoreDescriptorsPosition: RestoreDescriptorsPosition
};