forked from enviPath/enviPy
981 lines
28 KiB
JavaScript
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
|
|
};
|