forked from enviPath/enviPy
1561 lines
43 KiB
JavaScript
1561 lines
43 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 Set = require('../util/set');
|
|
var Vec2 = require('../util/vec2');
|
|
var op = require('./op');
|
|
var utils = require('./tool/utils');
|
|
|
|
var Struct = require('../chem/struct');
|
|
var closest = require('./closest');
|
|
var uniq = require('lodash').uniq;
|
|
|
|
//
|
|
// Undo/redo actions
|
|
//
|
|
function Action() {
|
|
this.operations = [];
|
|
}
|
|
|
|
Action.prototype.addOp = function (operation, restruct) {
|
|
if (!restruct || !operation.isDummy(restruct))
|
|
this.operations.push(operation);
|
|
return operation;
|
|
};
|
|
|
|
Action.prototype.mergeWith = function (action) {
|
|
this.operations = this.operations.concat(action.operations);
|
|
return this;
|
|
};
|
|
|
|
// Perform action and return inverted one
|
|
Action.prototype.perform = function (restruct) {
|
|
var action = new Action();
|
|
|
|
this.operations.forEach(function (operation) {
|
|
action.addOp(operation.perform(restruct));
|
|
});
|
|
|
|
action.operations.reverse();
|
|
return action;
|
|
};
|
|
|
|
Action.prototype.isDummy = function (restruct) {
|
|
return this.operations.find(function (operation) {
|
|
return restruct ? !operation.isDummy(restruct) : true; // TODO [RB] the condition is always true for op.* operations
|
|
}) === undefined;
|
|
};
|
|
|
|
// Add action operation to remove atom from s-group if needed
|
|
function removeAtomFromSgroupIfNeeded(action, restruct, id) {
|
|
var sgroups = atomGetSGroups(restruct, id);
|
|
|
|
if (sgroups.length > 0) {
|
|
sgroups.forEach(function (sid) {
|
|
action.addOp(new op.SGroupAtomRemove(sid, id));
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Add action operations to remove whole s-group if needed
|
|
function removeSgroupIfNeeded(action, restruct, atoms) {
|
|
var struct = restruct.molecule;
|
|
var sgCounts = {};
|
|
|
|
atoms.forEach(function (id) {
|
|
var sgroups = atomGetSGroups(restruct, id);
|
|
|
|
sgroups.forEach(function (sid) {
|
|
sgCounts[sid] = sgCounts[sid] ? (sgCounts[sid] + 1) : 1;
|
|
});
|
|
});
|
|
|
|
for (var key in sgCounts) {
|
|
var sid = parseInt(key);
|
|
var sG = restruct.sgroups.get(sid).item;
|
|
var sgAtoms = Struct.SGroup.getAtoms(restruct.molecule, sG);
|
|
|
|
if (sgAtoms.length == sgCounts[sid]) {
|
|
// delete whole s-group
|
|
var sgroup = struct.sgroups.get(sid);
|
|
action.mergeWith(sGroupAttributeAction(sid, sgroup.getAttrs()));
|
|
action.addOp(new op.SGroupRemoveFromHierarchy(sid));
|
|
action.addOp(new op.SGroupDelete(sid));
|
|
}
|
|
}
|
|
}
|
|
|
|
function fromMultipleMove(restruct, lists, d) { // eslint-disable-line max-statements
|
|
d = new Vec2(d);
|
|
|
|
var action = new Action();
|
|
var i;
|
|
|
|
var struct = restruct.molecule;
|
|
var bondlist = [];
|
|
var loops = Set.empty();
|
|
var atomsToInvalidate = Set.empty();
|
|
|
|
if (lists.atoms) {
|
|
var atomSet = Set.fromList(lists.atoms);
|
|
restruct.bonds.each(function (bid, bond) {
|
|
if (Set.contains(atomSet, bond.b.begin) && Set.contains(atomSet, bond.b.end)) {
|
|
bondlist.push(bid);
|
|
// add all adjacent loops
|
|
// those that are not completely inside the structure will get redrawn anyway
|
|
['hb1', 'hb2'].forEach(function (hb) {
|
|
var loop = struct.halfBonds.get(bond.b[hb]).loop;
|
|
if (loop >= 0)
|
|
Set.add(loops, loop);
|
|
}, this);
|
|
} else if (Set.contains(atomSet, bond.b.begin)) {
|
|
Set.add(atomsToInvalidate, bond.b.begin);
|
|
} else if (Set.contains(atomSet, bond.b.end)) {
|
|
Set.add(atomsToInvalidate, bond.b.end);
|
|
}
|
|
}, this);
|
|
for (i = 0; i < bondlist.length; ++i)
|
|
action.addOp(new op.BondMove(bondlist[i], d));
|
|
Set.each(loops, function (loopId) {
|
|
if (restruct.reloops.get(loopId) && restruct.reloops.get(loopId).visel) // hack
|
|
action.addOp(new op.LoopMove(loopId, d));
|
|
}, this);
|
|
for (i = 0; i < lists.atoms.length; ++i) {
|
|
var aid = lists.atoms[i];
|
|
action.addOp(new op.AtomMove(aid, d, !Set.contains(atomsToInvalidate, aid)));
|
|
}
|
|
}
|
|
|
|
if (lists.rxnArrows) {
|
|
for (i = 0; i < lists.rxnArrows.length; ++i)
|
|
action.addOp(new op.RxnArrowMove(lists.rxnArrows[i], d, true));
|
|
}
|
|
|
|
if (lists.rxnPluses) {
|
|
for (i = 0; i < lists.rxnPluses.length; ++i)
|
|
action.addOp(new op.RxnPlusMove(lists.rxnPluses[i], d, true));
|
|
}
|
|
|
|
if (lists.sgroupData) {
|
|
for (i = 0; i < lists.sgroupData.length; ++i)
|
|
action.addOp(new op.SGroupDataMove(lists.sgroupData[i], d));
|
|
}
|
|
|
|
if (lists.chiralFlags) {
|
|
for (i = 0; i < lists.chiralFlags.length; ++i)
|
|
action.addOp(new op.ChiralFlagMove(d));
|
|
}
|
|
|
|
return action.perform(restruct);
|
|
}
|
|
|
|
function fromAtomsAttrs(restruct, ids, attrs, reset) {
|
|
var action = new Action();
|
|
(typeof (ids) === 'number' ? [ids] : ids).forEach(function (id) {
|
|
for (var key in Struct.Atom.attrlist) {
|
|
var value;
|
|
if (key in attrs)
|
|
value = attrs[key];
|
|
else if (reset)
|
|
value = Struct.Atom.attrGetDefault(key);
|
|
else
|
|
continue; // eslint-disable-line no-continue
|
|
action.addOp(new op.AtomAttr(id, key, value));
|
|
}
|
|
if (!reset && 'label' in attrs && attrs.label != null && attrs.label !== 'L#' && !attrs['atomList'])
|
|
action.addOp(new op.AtomAttr(id, 'atomList', null));
|
|
}, this);
|
|
return action.perform(restruct);
|
|
}
|
|
|
|
function fromBondAttrs(restruct, id, attrs, flip, reset) { // eslint-disable-line max-params
|
|
var action = new Action();
|
|
|
|
for (var key in Struct.Bond.attrlist) {
|
|
var value;
|
|
if (key in attrs)
|
|
value = attrs[key];
|
|
else if (reset)
|
|
value = Struct.Bond.attrGetDefault(key);
|
|
else
|
|
continue; // eslint-disable-line no-continue
|
|
action.addOp(new op.BondAttr(id, key, value));
|
|
}
|
|
if (flip)
|
|
action.mergeWith(toBondFlipping(restruct.molecule, id));
|
|
return action.perform(restruct);
|
|
}
|
|
|
|
function fromAtomAddition(resctruct, pos, atom) {
|
|
atom = Object.assign({}, atom);
|
|
var action = new Action();
|
|
atom.fragment = action.addOp(new op.FragmentAdd().perform(resctruct)).frid;
|
|
action.addOp(new op.AtomAdd(atom, pos).perform(resctruct));
|
|
return action;
|
|
}
|
|
|
|
function mergeFragments(action, restruct, frid, frid2) {
|
|
var struct = restruct.molecule;
|
|
if (frid2 != frid && (typeof frid2 === 'number')) {
|
|
var rgid = Struct.RGroup.findRGroupByFragment(struct.rgroups, frid2);
|
|
if (!(typeof rgid === 'undefined'))
|
|
action.mergeWith(fromRGroupFragment(restruct, null, frid2));
|
|
|
|
struct.atoms.each(function (aid, atom) {
|
|
if (atom.fragment == frid2)
|
|
action.addOp(new op.AtomAttr(aid, 'fragment', frid).perform(restruct));
|
|
});
|
|
action.addOp(new op.FragmentDelete(frid2).perform(restruct));
|
|
}
|
|
}
|
|
|
|
// Get new atom id/label and pos for bond being added to existing atom
|
|
function atomForNewBond(restruct, id) { // eslint-disable-line max-statements
|
|
var neighbours = [];
|
|
var pos = atomGetPos(restruct, id);
|
|
|
|
atomGetNeighbors(restruct, id).forEach(function (nei) {
|
|
var neiPos = atomGetPos(restruct, nei.aid);
|
|
|
|
if (Vec2.dist(pos, neiPos) < 0.1)
|
|
return;
|
|
|
|
neighbours.push({ id: nei.aid, v: Vec2.diff(neiPos, pos) });
|
|
});
|
|
|
|
neighbours.sort(function (nei1, nei2) {
|
|
return Math.atan2(nei1.v.y, nei1.v.x) - Math.atan2(nei2.v.y, nei2.v.x);
|
|
});
|
|
|
|
var i;
|
|
var maxI = 0;
|
|
var angle;
|
|
var maxAngle = 0;
|
|
|
|
// TODO: impove layout: tree, ...
|
|
|
|
for (i = 0; i < neighbours.length; i++) {
|
|
angle = Vec2.angle(neighbours[i].v, neighbours[(i + 1) % neighbours.length].v);
|
|
|
|
if (angle < 0)
|
|
angle += 2 * Math.PI;
|
|
|
|
if (angle > maxAngle) {
|
|
maxI = i;
|
|
maxAngle = angle;
|
|
}
|
|
}
|
|
|
|
var v = new Vec2(1, 0);
|
|
|
|
if (neighbours.length > 0) {
|
|
if (neighbours.length == 1) {
|
|
maxAngle = -(4 * Math.PI / 3);
|
|
|
|
// zig-zag
|
|
var nei = atomGetNeighbors(restruct, id)[0];
|
|
if (atomGetDegree(restruct, nei.aid) > 1) {
|
|
var neiNeighbours = [];
|
|
var neiPos = atomGetPos(restruct, nei.aid);
|
|
var neiV = Vec2.diff(pos, neiPos);
|
|
var neiAngle = Math.atan2(neiV.y, neiV.x);
|
|
|
|
atomGetNeighbors(restruct, nei.aid).forEach(function (neiNei) {
|
|
var neiNeiPos = atomGetPos(restruct, neiNei.aid);
|
|
|
|
if (neiNei.bid == nei.bid || Vec2.dist(neiPos, neiNeiPos) < 0.1)
|
|
return;
|
|
|
|
var vDiff = Vec2.diff(neiNeiPos, neiPos);
|
|
var ang = Math.atan2(vDiff.y, vDiff.x) - neiAngle;
|
|
|
|
if (ang < 0)
|
|
ang += 2 * Math.PI;
|
|
|
|
neiNeighbours.push(ang);
|
|
});
|
|
neiNeighbours.sort(function (nei1, nei2) {
|
|
return nei1 - nei2;
|
|
});
|
|
|
|
if (neiNeighbours[0] <= Math.PI * 1.01 && neiNeighbours[neiNeighbours.length - 1] <= 1.01 * Math.PI)
|
|
maxAngle *= -1;
|
|
}
|
|
}
|
|
|
|
angle = (maxAngle / 2) + Math.atan2(neighbours[maxI].v.y, neighbours[maxI].v.x);
|
|
|
|
v = v.rotate(angle);
|
|
}
|
|
|
|
v.add_(pos); // eslint-disable-line no-underscore-dangle
|
|
|
|
var a = closest.atom(restruct, v, null, 0.1);
|
|
|
|
if (a == null)
|
|
a = { label: 'C' };
|
|
else
|
|
a = a.id;
|
|
|
|
return { atom: a, pos: v };
|
|
}
|
|
|
|
function fromBondAddition(restruct, bond, begin, end, pos, pos2) { // eslint-disable-line max-params, max-statements
|
|
if (end === undefined) {
|
|
var atom = atomForNewBond(restruct, begin);
|
|
end = atom.atom;
|
|
pos = atom.pos;
|
|
}
|
|
var action = new Action();
|
|
|
|
var frid = null;
|
|
|
|
if (!(typeof begin === "number")) {
|
|
if (typeof end === "number")
|
|
frid = atomGetAttr(restruct, end, 'fragment');
|
|
} else {
|
|
frid = atomGetAttr(restruct, begin, 'fragment');
|
|
if (typeof end === "number") {
|
|
var frid2 = atomGetAttr(restruct, end, 'fragment');
|
|
mergeFragments(action, restruct, frid, frid2);
|
|
}
|
|
}
|
|
|
|
if (frid == null)
|
|
frid = action.addOp(new op.FragmentAdd().perform(restruct)).frid;
|
|
|
|
if (!(typeof begin === "number")) {
|
|
begin.fragment = frid;
|
|
begin = action.addOp(new op.AtomAdd(begin, pos).perform(restruct)).data.aid;
|
|
|
|
pos = pos2;
|
|
} else if (atomGetAttr(restruct, begin, 'label') === '*') {
|
|
action.addOp(new op.AtomAttr(begin, 'label', 'C').perform(restruct));
|
|
}
|
|
|
|
|
|
if (!(typeof end === "number")) {
|
|
end.fragment = frid;
|
|
// TODO: <op>.data.aid here is a hack, need a better way to access the id of a newly created atom
|
|
end = action.addOp(new op.AtomAdd(end, pos).perform(restruct)).data.aid;
|
|
if (typeof begin === "number") {
|
|
atomGetSGroups(restruct, begin).forEach(function (sid) {
|
|
action.addOp(new op.SGroupAtomAdd(sid, end).perform(restruct));
|
|
}, this);
|
|
}
|
|
} else if (atomGetAttr(restruct, end, 'label') === '*') {
|
|
action.addOp(new op.AtomAttr(end, 'label', 'C').perform(restruct));
|
|
}
|
|
|
|
var bid = action.addOp(new op.BondAdd(begin, end, bond).perform(restruct)).data.bid;
|
|
|
|
action.operations.reverse();
|
|
|
|
return [action, begin, end, bid];
|
|
}
|
|
|
|
function fromArrowAddition(restruct, pos) {
|
|
var action = new Action();
|
|
if (restruct.molecule.rxnArrows.count() < 1)
|
|
action.addOp(new op.RxnArrowAdd(pos).perform(restruct));
|
|
return action;
|
|
}
|
|
|
|
function fromArrowDeletion(restruct, id) {
|
|
var action = new Action();
|
|
action.addOp(new op.RxnArrowDelete(id));
|
|
return action.perform(restruct);
|
|
}
|
|
|
|
function fromChiralFlagAddition(restruct, pos) { // eslint-disable-line no-unused-vars
|
|
var action = new Action();
|
|
var struct = restruct.molecule;
|
|
if (restruct.chiralFlags.count() < 1) {
|
|
if (!pos) {
|
|
var bb = struct.getCoordBoundingBox();
|
|
var posY = !struct.isBlank() ? bb.min.y - 1 : bb.min.y + 1;
|
|
pos = new Vec2(bb.max.x, posY);
|
|
}
|
|
action.addOp(new op.ChiralFlagAdd(pos).perform(restruct));
|
|
}
|
|
return action;
|
|
}
|
|
|
|
function fromChiralFlagDeletion(restruct) {
|
|
var action = new Action();
|
|
action.addOp(new op.ChiralFlagDelete());
|
|
return action.perform(restruct);
|
|
}
|
|
|
|
function fromPlusAddition(restruct, pos) {
|
|
var action = new Action();
|
|
action.addOp(new op.RxnPlusAdd(pos).perform(restruct));
|
|
return action;
|
|
}
|
|
|
|
function fromPlusDeletion(restruct, id) {
|
|
var action = new Action();
|
|
action.addOp(new op.RxnPlusDelete(id));
|
|
return action.perform(restruct);
|
|
}
|
|
|
|
function fromAtomDeletion(restruct, id) {
|
|
var action = new Action();
|
|
var atomsToRemove = [];
|
|
|
|
var frid = restruct.molecule.atoms.get(id).fragment;
|
|
|
|
atomGetNeighbors(restruct, id).forEach(function (nei) {
|
|
action.addOp(new op.BondDelete(nei.bid));// [RB] !!
|
|
if (atomGetDegree(restruct, nei.aid) == 1) {
|
|
if (removeAtomFromSgroupIfNeeded(action, restruct, nei.aid))
|
|
atomsToRemove.push(nei.aid);
|
|
|
|
action.addOp(new op.AtomDelete(nei.aid));
|
|
}
|
|
}, this);
|
|
|
|
if (removeAtomFromSgroupIfNeeded(action, restruct, id))
|
|
atomsToRemove.push(id);
|
|
|
|
action.addOp(new op.AtomDelete(id));
|
|
|
|
removeSgroupIfNeeded(action, restruct, atomsToRemove);
|
|
|
|
action = action.perform(restruct);
|
|
|
|
action.mergeWith(new FromFragmentSplit(restruct, frid));
|
|
|
|
return action;
|
|
}
|
|
|
|
function fromBondDeletion(restruct, id) {
|
|
var action = new Action();
|
|
var bond = restruct.molecule.bonds.get(id);
|
|
var frid = restruct.molecule.atoms.get(bond.begin).fragment;
|
|
var atomsToRemove = [];
|
|
|
|
action.addOp(new op.BondDelete(id));
|
|
|
|
if (atomGetDegree(restruct, bond.begin) == 1) {
|
|
if (removeAtomFromSgroupIfNeeded(action, restruct, bond.begin))
|
|
atomsToRemove.push(bond.begin);
|
|
|
|
action.addOp(new op.AtomDelete(bond.begin));
|
|
}
|
|
|
|
if (atomGetDegree(restruct, bond.end) == 1) {
|
|
if (removeAtomFromSgroupIfNeeded(action, restruct, bond.end))
|
|
atomsToRemove.push(bond.end);
|
|
|
|
action.addOp(new op.AtomDelete(bond.end));
|
|
}
|
|
|
|
removeSgroupIfNeeded(action, restruct, atomsToRemove);
|
|
|
|
action = action.perform(restruct);
|
|
|
|
action.mergeWith(new FromFragmentSplit(restruct, frid));
|
|
|
|
return action;
|
|
}
|
|
|
|
function FromFragmentSplit(restruct, frid) { // TODO [RB] the thing is too tricky :) need something else in future
|
|
var action = new Action();
|
|
var rgid = Struct.RGroup.findRGroupByFragment(restruct.molecule.rgroups, frid);
|
|
|
|
restruct.molecule.atoms.each(function (aid, atom) {
|
|
if (atom.fragment === frid) {
|
|
var newfrid = action.addOp(new op.FragmentAdd().perform(restruct)).frid;
|
|
var processAtom = function (aid1) { // eslint-disable-line func-style
|
|
action.addOp(new op.AtomAttr(aid1, 'fragment', newfrid).perform(restruct));
|
|
atomGetNeighbors(restruct, aid1).forEach(function (nei) {
|
|
if (restruct.molecule.atoms.get(nei.aid).fragment === frid)
|
|
processAtom(nei.aid);
|
|
});
|
|
};
|
|
processAtom(aid);
|
|
if (rgid)
|
|
action.mergeWith(fromRGroupFragment(restruct, rgid, newfrid));
|
|
}
|
|
});
|
|
|
|
if (frid !== -1) {
|
|
action.mergeWith(fromRGroupFragment(restruct, 0, frid));
|
|
action.addOp(new op.FragmentDelete(frid).perform(restruct));
|
|
action.mergeWith(fromUpdateIfThen(restruct, 0, rgid));
|
|
}
|
|
|
|
return action;
|
|
}
|
|
|
|
function fromFragmentDeletion(restruct, selection) { // eslint-disable-line max-statements
|
|
console.assert(!!selection);
|
|
var action = new Action();
|
|
var atomsToRemove = [];
|
|
var frids = [];
|
|
selection = { // TODO: refactor me
|
|
atoms: selection.atoms || [],
|
|
bonds: selection.bonds || [],
|
|
rxnPluses: selection.rxnPluses || [],
|
|
rxnArrows: selection.rxnArrows || [],
|
|
sgroupData: selection.sgroupData || [],
|
|
chiralFlags: selection.chiralFlags || []
|
|
};
|
|
|
|
var actionRemoveDataSGroups = new Action();
|
|
selection.sgroupData.forEach(function (id) {
|
|
actionRemoveDataSGroups.mergeWith(fromSgroupDeletion(restruct, id));
|
|
}, this);
|
|
|
|
selection.atoms.forEach(function (aid) {
|
|
atomGetNeighbors(restruct, aid).forEach(function (nei) {
|
|
if (selection.bonds.indexOf(nei.bid) == -1)
|
|
selection.bonds = selection.bonds.concat([nei.bid]);
|
|
}, this);
|
|
}, this);
|
|
|
|
selection.bonds.forEach(function (bid) {
|
|
action.addOp(new op.BondDelete(bid));
|
|
|
|
var bond = restruct.molecule.bonds.get(bid);
|
|
var frid = restruct.molecule.atoms.get(bond.begin).fragment;
|
|
if (frids.indexOf(frid) < 0)
|
|
frids.push(frid);
|
|
|
|
if (selection.atoms.indexOf(bond.begin) == -1 && atomGetDegree(restruct, bond.begin) == 1) {
|
|
if (removeAtomFromSgroupIfNeeded(action, restruct, bond.begin))
|
|
atomsToRemove.push(bond.begin);
|
|
|
|
action.addOp(new op.AtomDelete(bond.begin));
|
|
}
|
|
if (selection.atoms.indexOf(bond.end) == -1 && atomGetDegree(restruct, bond.end) == 1) {
|
|
if (removeAtomFromSgroupIfNeeded(action, restruct, bond.end))
|
|
atomsToRemove.push(bond.end);
|
|
|
|
action.addOp(new op.AtomDelete(bond.end));
|
|
}
|
|
}, this);
|
|
|
|
|
|
selection.atoms.forEach(function (aid) {
|
|
var frid3 = restruct.molecule.atoms.get(aid).fragment;
|
|
if (frids.indexOf(frid3) < 0)
|
|
frids.push(frid3);
|
|
|
|
if (removeAtomFromSgroupIfNeeded(action, restruct, aid))
|
|
atomsToRemove.push(aid);
|
|
|
|
action.addOp(new op.AtomDelete(aid));
|
|
}, this);
|
|
|
|
removeSgroupIfNeeded(action, restruct, atomsToRemove);
|
|
|
|
selection.rxnArrows.forEach(function (id) {
|
|
action.addOp(new op.RxnArrowDelete(id));
|
|
}, this);
|
|
|
|
selection.rxnPluses.forEach(function (id) {
|
|
action.addOp(new op.RxnPlusDelete(id));
|
|
}, this);
|
|
|
|
selection.chiralFlags.forEach(function (id) {
|
|
action.addOp(new op.ChiralFlagDelete(id));
|
|
}, this);
|
|
|
|
action = action.perform(restruct);
|
|
|
|
while (frids.length > 0)
|
|
action.mergeWith(new FromFragmentSplit(restruct, frids.pop()));
|
|
|
|
action.mergeWith(actionRemoveDataSGroups);
|
|
|
|
return action;
|
|
}
|
|
|
|
function fromAtomMerge(restruct, srcId, dstId) {
|
|
var fragAction = new Action();
|
|
var srcFrid = atomGetAttr(restruct, srcId, 'fragment');
|
|
var dstFrid = atomGetAttr(restruct, dstId, 'fragment');
|
|
if (srcFrid != dstFrid)
|
|
mergeFragments(fragAction, restruct, srcFrid, dstFrid);
|
|
|
|
var action = new Action();
|
|
|
|
atomGetNeighbors(restruct, srcId).forEach(function (nei) {
|
|
var bond = restruct.molecule.bonds.get(nei.bid);
|
|
var begin, end;
|
|
|
|
if (bond.begin == nei.aid) {
|
|
begin = nei.aid;
|
|
end = dstId;
|
|
} else {
|
|
begin = dstId;
|
|
end = nei.aid;
|
|
}
|
|
if (dstId != bond.begin && dstId != bond.end && restruct.molecule.findBondId(begin, end) == -1) // TODO: improve this {
|
|
action.addOp(new op.BondAdd(begin, end, bond));
|
|
action.addOp(new op.BondDelete(nei.bid));
|
|
}, this);
|
|
|
|
var attrs = Struct.Atom.getAttrHash(restruct.molecule.atoms.get(srcId));
|
|
|
|
if (atomGetDegree(restruct, srcId) == 1 && attrs['label'] === '*')
|
|
attrs['label'] = 'C';
|
|
for (var key in attrs)
|
|
if (attrs.hasOwnProperty(key)) action.addOp(new op.AtomAttr(dstId, key, attrs[key]));
|
|
|
|
var sgChanged = removeAtomFromSgroupIfNeeded(action, restruct, srcId);
|
|
|
|
action.addOp(new op.AtomDelete(srcId));
|
|
|
|
if (sgChanged)
|
|
removeSgroupIfNeeded(action, restruct, [srcId]);
|
|
|
|
return action.perform(restruct).mergeWith(fragAction);
|
|
}
|
|
|
|
function toBondFlipping(struct, id) {
|
|
var bond = struct.bonds.get(id);
|
|
|
|
var action = new Action();
|
|
action.addOp(new op.BondDelete(id));
|
|
action.addOp(new op.BondAdd(bond.end, bond.begin, bond)).data.bid = id;
|
|
return action;
|
|
}
|
|
|
|
function fromBondFlipping(restruct, bid) {
|
|
return toBondFlipping(restruct.molecule, bid).perform(restruct);
|
|
}
|
|
|
|
function fromTemplateOnCanvas(restruct, pos, angle, template) {
|
|
var action = new Action();
|
|
var frag = template.molecule;
|
|
|
|
var fragAction = new op.FragmentAdd().perform(restruct);
|
|
|
|
var map = {};
|
|
|
|
// Only template atom label matters for now
|
|
frag.atoms.each(function (aid, atom) {
|
|
var operation;
|
|
var attrs = Struct.Atom.getAttrHash(atom);
|
|
attrs.fragment = fragAction.frid;
|
|
|
|
action.addOp(
|
|
operation = new op.AtomAdd(
|
|
attrs,
|
|
Vec2.diff(atom.pp, template.xy0).rotate(angle).add(pos)
|
|
).perform(restruct)
|
|
);
|
|
|
|
map[aid] = operation.data.aid;
|
|
});
|
|
|
|
frag.bonds.each(function (bid, bond) {
|
|
action.addOp(
|
|
new op.BondAdd(
|
|
map[bond.begin],
|
|
map[bond.end],
|
|
bond
|
|
).perform(restruct)
|
|
);
|
|
});
|
|
|
|
action.operations.reverse();
|
|
action.addOp(fragAction);
|
|
|
|
return action;
|
|
}
|
|
|
|
function atomAddToSGroups(restruct, sgroups, aid) {
|
|
var action = new Action();
|
|
sgroups.forEach(function (sid) {
|
|
action.addOp(new op.SGroupAtomAdd(sid, aid).perform(restruct));
|
|
}, this);
|
|
return action;
|
|
}
|
|
|
|
function fromTemplateOnAtom(restruct, aid, angle, extraBond, template) { // eslint-disable-line max-statements, max-params
|
|
var action = new Action();
|
|
var frag = template.molecule;
|
|
var struct = restruct.molecule;
|
|
var atom = struct.atoms.get(aid);
|
|
var aid0 = aid; // the atom that was clicked on
|
|
var aid1 = null; // the atom on the other end of the extra bond, if any
|
|
var sgroups = atomGetSGroups(restruct, aid);
|
|
|
|
var frid = atomGetAttr(restruct, aid, 'fragment');
|
|
|
|
var map = {};
|
|
var xy0 = frag.atoms.get(template.aid).pp;
|
|
|
|
if (extraBond) {
|
|
// create extra bond after click on atom
|
|
if (angle == null) {
|
|
var middleAtom = atomForNewBond(restruct, aid);
|
|
var actionRes = fromBondAddition(restruct, { type: 1 }, aid, middleAtom.atom, middleAtom.pos.get_xy0());
|
|
action = actionRes[0];
|
|
action.operations.reverse();
|
|
aid1 = aid = actionRes[2];
|
|
} else {
|
|
var operation;
|
|
|
|
action.addOp(
|
|
operation = new op.AtomAdd(
|
|
{ label: 'C', fragment: frid },
|
|
(new Vec2(1, 0)).rotate(angle).add(atom.pp).get_xy0()
|
|
).perform(restruct)
|
|
);
|
|
|
|
action.addOp(
|
|
new op.BondAdd(
|
|
aid,
|
|
operation.data.aid,
|
|
{ type: 1 }
|
|
).perform(restruct)
|
|
);
|
|
|
|
aid1 = aid = operation.data.aid;
|
|
action.mergeWith(atomAddToSGroups(restruct, sgroups, aid));
|
|
}
|
|
|
|
var atom0 = atom;
|
|
atom = struct.atoms.get(aid);
|
|
var delta = utils.calcAngle(atom0.pp, atom.pp) - template.angle0;
|
|
} else {
|
|
if (angle == null) {
|
|
middleAtom = atomForNewBond(restruct, aid);
|
|
angle = utils.calcAngle(atom.pp, middleAtom.pos);
|
|
}
|
|
delta = angle - template.angle0;
|
|
}
|
|
|
|
frag.atoms.each(function (id, a) {
|
|
var attrs = Struct.Atom.getAttrHash(a);
|
|
attrs.fragment = frid;
|
|
if (id == template.aid) {
|
|
action.mergeWith(fromAtomsAttrs(restruct, aid, attrs, true));
|
|
map[id] = aid;
|
|
} else {
|
|
var v;
|
|
|
|
v = Vec2.diff(a.pp, xy0).rotate(delta).add(atom.pp);
|
|
|
|
action.addOp(
|
|
operation = new op.AtomAdd(
|
|
attrs,
|
|
v.get_xy0()
|
|
).perform(restruct)
|
|
);
|
|
map[id] = operation.data.aid;
|
|
}
|
|
if (map[id] - 0 !== aid0 - 0 && map[id] - 0 !== aid1 - 0)
|
|
action.mergeWith(atomAddToSGroups(restruct, sgroups, map[id]));
|
|
});
|
|
|
|
frag.bonds.each(function (bid, bond) {
|
|
action.addOp(
|
|
new op.BondAdd(
|
|
map[bond.begin],
|
|
map[bond.end],
|
|
bond
|
|
).perform(restruct)
|
|
);
|
|
});
|
|
|
|
action.operations.reverse();
|
|
|
|
return action;
|
|
}
|
|
|
|
function fromTemplateOnBond(restruct, bid, template, flip) { // eslint-disable-line max-statements
|
|
var action = new Action();
|
|
var frag = template.molecule;
|
|
var struct = restruct.molecule;
|
|
|
|
var bond = struct.bonds.get(bid);
|
|
var begin = struct.atoms.get(bond.begin);
|
|
var end = struct.atoms.get(bond.end);
|
|
var sgroups = Set.list(Set.intersection(
|
|
Set.fromList(atomGetSGroups(restruct, bond.begin)),
|
|
Set.fromList(atomGetSGroups(restruct, bond.end))));
|
|
|
|
var frBond = frag.bonds.get(template.bid);
|
|
var frBegin;
|
|
var frEnd;
|
|
|
|
var frid = atomGetAttr(restruct, bond.begin, 'fragment');
|
|
|
|
var map = {};
|
|
|
|
if (flip) {
|
|
frBegin = frag.atoms.get(frBond.end);
|
|
frEnd = frag.atoms.get(frBond.begin);
|
|
map[frBond.end] = bond.begin;
|
|
map[frBond.begin] = bond.end;
|
|
} else {
|
|
frBegin = frag.atoms.get(frBond.begin);
|
|
frEnd = frag.atoms.get(frBond.end);
|
|
map[frBond.begin] = bond.begin;
|
|
map[frBond.end] = bond.end;
|
|
}
|
|
|
|
// calc angle
|
|
var angle = utils.calcAngle(begin.pp, end.pp) - utils.calcAngle(frBegin.pp, frEnd.pp);
|
|
var scale = Vec2.dist(begin.pp, end.pp) / Vec2.dist(frBegin.pp, frEnd.pp);
|
|
|
|
frag.atoms.each(function (id, a) {
|
|
var attrs = Struct.Atom.getAttrHash(a);
|
|
attrs.fragment = frid;
|
|
if (id == frBond.begin || id == frBond.end) {
|
|
action.mergeWith(fromAtomsAttrs(restruct, map[id], attrs, true));
|
|
return;
|
|
}
|
|
|
|
var v;
|
|
|
|
v = Vec2.diff(a.pp, frBegin.pp).rotate(angle).scaled(scale).add(begin.pp);
|
|
|
|
var mergeA = closest.atom(restruct, v, null, 0.1);
|
|
|
|
if (mergeA == null) {
|
|
var operation;
|
|
action.addOp(
|
|
operation = new op.AtomAdd(
|
|
attrs,
|
|
v
|
|
).perform(restruct)
|
|
);
|
|
|
|
map[id] = operation.data.aid;
|
|
action.mergeWith(atomAddToSGroups(restruct, sgroups, map[id]));
|
|
} else {
|
|
map[id] = mergeA.id;
|
|
action.mergeWith(fromAtomsAttrs(restruct, map[id], attrs, true));
|
|
// TODO [RB] need to merge fragments?
|
|
}
|
|
});
|
|
|
|
frag.bonds.each(function (id, bond) {
|
|
var existId = struct.findBondId(map[bond.begin], map[bond.end]);
|
|
if (existId == -1) {
|
|
action.addOp(
|
|
new op.BondAdd(
|
|
map[bond.begin],
|
|
map[bond.end],
|
|
bond
|
|
).perform(restruct));
|
|
} else {
|
|
action.mergeWith(fromBondAttrs(restruct, existId, frBond, false, true));
|
|
}
|
|
});
|
|
|
|
action.operations.reverse();
|
|
|
|
return action;
|
|
}
|
|
|
|
function fromChain(restruct, p0, v, nSect, atomId) { // eslint-disable-line max-params
|
|
var dx = Math.cos(Math.PI / 6);
|
|
var dy = Math.sin(Math.PI / 6);
|
|
|
|
var action = new Action();
|
|
|
|
var frid;
|
|
if (atomId != null)
|
|
frid = atomGetAttr(restruct, atomId, 'fragment');
|
|
else
|
|
frid = action.addOp(new op.FragmentAdd().perform(restruct)).frid;
|
|
|
|
var id0 = -1;
|
|
if (atomId != null)
|
|
id0 = atomId;
|
|
else
|
|
id0 = action.addOp(new op.AtomAdd({ label: 'C', fragment: frid }, p0).perform(restruct)).data.aid;
|
|
|
|
action.operations.reverse();
|
|
|
|
for (var i = 0; i < nSect; i++) {
|
|
var pos = new Vec2(dx * (i + 1), i & 1 ? 0 : dy).rotate(v).add(p0);
|
|
|
|
var a = closest.atom(restruct, pos, null, 0.1);
|
|
var ret = fromBondAddition(restruct, {}, id0, a ? a.id : {}, pos);
|
|
action = ret[0].mergeWith(action);
|
|
id0 = ret[2];
|
|
}
|
|
|
|
return action;
|
|
}
|
|
|
|
function fromNewCanvas(restruct, struct) {
|
|
var action = new Action();
|
|
|
|
action.addOp(new op.CanvasLoad(struct));
|
|
return action.perform(restruct);
|
|
}
|
|
|
|
function fromSgroupType(restruct, id, type) {
|
|
var sg = restruct.sgroups.get(id).item;
|
|
var curType = sg.type;
|
|
if (type && type != curType) {
|
|
var atoms = [].slice.call(Struct.SGroup.getAtoms(restruct.molecule, sg));
|
|
var attrs = sg.getAttrs();
|
|
var actionDeletion = fromSgroupDeletion(restruct, id); // [MK] order of execution is important, first delete then recreate
|
|
var actionAddition = fromSgroupAddition(restruct, type, atoms, attrs, id);
|
|
return actionAddition.mergeWith(actionDeletion); // the actions are already performed and reversed, so we merge them backwards
|
|
}
|
|
return new Action();
|
|
}
|
|
|
|
function fromSeveralSgroupAddition(restruct, type, atoms, attrs) {
|
|
const descriptors = attrs.fieldValue;
|
|
|
|
if (typeof descriptors === 'string' || type !== 'DAT')
|
|
return Action.fromSgroupAddition(restruct, type, atoms, attrs, restruct.molecule.sgroups.newId());
|
|
|
|
return descriptors.reduce((acc, fValue) => {
|
|
const localAttrs = Object.assign({}, attrs);
|
|
localAttrs.fieldValue = fValue;
|
|
|
|
return acc
|
|
.mergeWith(Action.fromSgroupAddition(restruct, type, atoms, localAttrs, restruct.molecule.sgroups.newId()));
|
|
}, new Action());
|
|
}
|
|
|
|
function fromSgroupAttrs(restruct, id, attrs) {
|
|
const action = new Action();
|
|
|
|
Object.keys(attrs)
|
|
.forEach(key => action.addOp(new op.SGroupAttr(id, key, attrs[key])));
|
|
|
|
return action.perform(restruct);
|
|
}
|
|
|
|
function sGroupAttributeAction(id, attrs) {
|
|
const action = new Action();
|
|
|
|
Object.keys(attrs)
|
|
.forEach(key => action.addOp(new op.SGroupAttr(id, key, attrs[key])));
|
|
|
|
return action;
|
|
}
|
|
|
|
function fromSgroupDeletion(restruct, id) { // eslint-disable-line max-statements
|
|
var action = new Action();
|
|
var struct = restruct.molecule;
|
|
|
|
var sG = restruct.sgroups.get(id).item;
|
|
if (sG.type === 'SRU') {
|
|
struct.sGroupsRecalcCrossBonds();
|
|
var neiAtoms = sG.neiAtoms;
|
|
|
|
neiAtoms.forEach(function (aid) {
|
|
if (atomGetAttr(restruct, aid, 'label') === '*')
|
|
action.addOp(new op.AtomAttr(aid, 'label', 'C'));
|
|
}, this);
|
|
}
|
|
|
|
var sg = struct.sgroups.get(id);
|
|
var atoms = Struct.SGroup.getAtoms(struct, sg);
|
|
var attrs = sg.getAttrs();
|
|
action.addOp(new op.SGroupRemoveFromHierarchy(id));
|
|
for (var i = 0; i < atoms.length; ++i)
|
|
action.addOp(new op.SGroupAtomRemove(id, atoms[i]));
|
|
action.addOp(new op.SGroupDelete(id));
|
|
|
|
action = action.perform(restruct);
|
|
|
|
action.mergeWith(sGroupAttributeAction(id, attrs));
|
|
|
|
return action;
|
|
}
|
|
|
|
function fromSgroupAddition(restruct, type, atoms, attrs, sgid, pp) { // eslint-disable-line max-params, max-statements
|
|
var action = new Action();
|
|
var i;
|
|
|
|
// TODO: shoud the id be generated when OpSGroupCreate is executed?
|
|
// if yes, how to pass it to the following operations?
|
|
sgid = sgid - 0 === sgid ? sgid : restruct.molecule.sgroups.newId();
|
|
|
|
action.addOp(new op.SGroupCreate(sgid, type, pp));
|
|
for (i = 0; i < atoms.length; i++)
|
|
action.addOp(new op.SGroupAtomAdd(sgid, atoms[i]));
|
|
action.addOp(type != 'DAT' ?
|
|
new op.SGroupAddToHierarchy(sgid) :
|
|
new op.SGroupAddToHierarchy(sgid, -1, []));
|
|
|
|
action = action.perform(restruct);
|
|
|
|
if (type === 'SRU') {
|
|
restruct.molecule.sGroupsRecalcCrossBonds();
|
|
var asteriskAction = new Action();
|
|
restruct.sgroups.get(sgid).item.neiAtoms.forEach(function (aid) {
|
|
var plainCarbon = restruct.atoms.get(aid).a.isPlainCarbon();
|
|
if (atomGetDegree(restruct, aid) == 1 && plainCarbon)
|
|
asteriskAction.addOp(new op.AtomAttr(aid, 'label', '*'));
|
|
}, this);
|
|
|
|
asteriskAction = asteriskAction.perform(restruct);
|
|
asteriskAction.mergeWith(action);
|
|
action = asteriskAction;
|
|
}
|
|
|
|
return fromSgroupAttrs(restruct, sgid, attrs).mergeWith(action);
|
|
}
|
|
|
|
function fromRGroupAttrs(restruct, id, attrs) {
|
|
var action = new Action();
|
|
for (var key in attrs) {
|
|
if (attrs.hasOwnProperty(key))
|
|
action.addOp(new op.RGroupAttr(id, key, attrs[key]));
|
|
}
|
|
|
|
return action.perform(restruct);
|
|
}
|
|
|
|
function fromRGroupFragment(restruct, rgidNew, frid) {
|
|
const action = new Action();
|
|
action.addOp(new op.RGroupFragment(rgidNew, frid));
|
|
return action.perform(restruct);
|
|
}
|
|
|
|
function fromUpdateIfThen(restruct, rgidNew, rgidOld) {
|
|
const action = new Action();
|
|
|
|
if (!restruct.molecule.rgroups.get(rgidOld))
|
|
action.addOp(new op.UpdateIfThen(rgidNew, rgidOld));
|
|
|
|
return action.perform(restruct);
|
|
}
|
|
|
|
// Should it be named structCenter?
|
|
function getAnchorPosition(clipboard) {
|
|
if (clipboard.atoms.length) {
|
|
var xmin = 1e50;
|
|
var ymin = xmin;
|
|
var xmax = -xmin;
|
|
var ymax = -ymin;
|
|
for (var i = 0; i < clipboard.atoms.length; i++) {
|
|
xmin = Math.min(xmin, clipboard.atoms[i].pp.x);
|
|
ymin = Math.min(ymin, clipboard.atoms[i].pp.y);
|
|
xmax = Math.max(xmax, clipboard.atoms[i].pp.x);
|
|
ymax = Math.max(ymax, clipboard.atoms[i].pp.y);
|
|
}
|
|
return new Vec2((xmin + xmax) / 2, (ymin + ymax) / 2); // TODO: check
|
|
} else if (clipboard.rxnArrows.length) {
|
|
return clipboard.rxnArrows[0].pp;
|
|
} else if (clipboard.rxnPluses.length) {
|
|
return clipboard.rxnPluses[0].pp;
|
|
} else if (clipboard.chiralFlags.length) {
|
|
return clipboard.chiralFlags[0].pp;
|
|
} else { // eslint-disable-line no-else-return
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// TODO: merge to bellow
|
|
function struct2Clipboard(struct) { // eslint-disable-line max-statements
|
|
console.assert(!struct.isBlank(), 'Empty struct');
|
|
|
|
var selection = structSelection(struct);
|
|
|
|
var clipboard = {
|
|
atoms: [],
|
|
bonds: [],
|
|
sgroups: [],
|
|
rxnArrows: [],
|
|
rxnPluses: [],
|
|
chiralFlags: [],
|
|
rgmap: {},
|
|
rgroups: {}
|
|
};
|
|
|
|
var mapping = {};
|
|
selection.atoms.forEach(function (id) {
|
|
var newAtom = new Struct.Atom(struct.atoms.get(id));
|
|
newAtom.pos = newAtom.pp;
|
|
mapping[id] = clipboard.atoms.push(new Struct.Atom(newAtom)) - 1;
|
|
});
|
|
|
|
selection.bonds.forEach(function (id) {
|
|
var newBond = new Struct.Bond(struct.bonds.get(id));
|
|
newBond.begin = mapping[newBond.begin];
|
|
newBond.end = mapping[newBond.end];
|
|
clipboard.bonds.push(new Struct.Bond(newBond));
|
|
});
|
|
|
|
var sgroupList = struct.getSGroupsInAtomSet(selection.atoms);
|
|
|
|
sgroupList.forEach(function (sid) {
|
|
var sgroup = struct.sgroups.get(sid);
|
|
var sgAtoms = Struct.SGroup.getAtoms(struct, sgroup);
|
|
var sgroupInfo = {
|
|
type: sgroup.type,
|
|
attrs: sgroup.getAttrs(),
|
|
atoms: [].slice.call(sgAtoms),
|
|
pp: sgroup.pp
|
|
};
|
|
|
|
for (var i = 0; i < sgroupInfo.atoms.length; i++)
|
|
sgroupInfo.atoms[i] = mapping[sgroupInfo.atoms[i]];
|
|
|
|
clipboard.sgroups.push(sgroupInfo);
|
|
}, this);
|
|
|
|
selection.rxnArrows.forEach(function (id) {
|
|
var arrow = new Struct.RxnArrow(struct.rxnArrows.get(id));
|
|
arrow.pos = arrow.pp;
|
|
clipboard.rxnArrows.push(arrow);
|
|
});
|
|
|
|
selection.rxnPluses.forEach(function (id) {
|
|
var plus = new Struct.RxnPlus(struct.rxnPluses.get(id));
|
|
plus.pos = plus.pp;
|
|
clipboard.rxnPluses.push(plus);
|
|
});
|
|
|
|
// r-groups
|
|
var atomFragments = {};
|
|
var fragments = Set.empty();
|
|
selection.atoms.forEach(function (id) {
|
|
var atom = struct.atoms.get(id);
|
|
var frag = atom.fragment;
|
|
atomFragments[id] = frag;
|
|
Set.add(fragments, frag);
|
|
});
|
|
|
|
var rgids = Set.empty();
|
|
Set.each(fragments, function (frid) {
|
|
var atoms = getFragmentAtoms(struct, frid);
|
|
for (var i = 0; i < atoms.length; ++i) {
|
|
if (!Set.contains(atomFragments, atoms[i]))
|
|
return;
|
|
}
|
|
var rgid = Struct.RGroup.findRGroupByFragment(struct.rgroups, frid);
|
|
clipboard.rgmap[frid] = rgid;
|
|
Set.add(rgids, rgid);
|
|
}, this);
|
|
|
|
Set.each(rgids, function (id) {
|
|
clipboard.rgroups[id] = struct.rgroups.get(id).getAttrs();
|
|
}, this);
|
|
|
|
return clipboard;
|
|
}
|
|
|
|
function fromPaste(restruct, pstruct, point) { // eslint-disable-line max-statements
|
|
var clipboard = struct2Clipboard(pstruct);
|
|
var offset = point ? Vec2.diff(point, getAnchorPosition(clipboard)) : new Vec2();
|
|
var action = new Action();
|
|
var amap = {};
|
|
var fmap = {};
|
|
// atoms
|
|
for (var aid = 0; aid < clipboard.atoms.length; aid++) {
|
|
var atom = Object.assign({}, clipboard.atoms[aid]);
|
|
if (!(atom.fragment in fmap))
|
|
fmap[atom.fragment] = action.addOp(new op.FragmentAdd().perform(restruct)).frid;
|
|
atom.fragment = fmap[atom.fragment];
|
|
amap[aid] = action.addOp(new op.AtomAdd(atom, atom.pp.add(offset)).perform(restruct)).data.aid;
|
|
}
|
|
|
|
var rgnew = [];
|
|
for (var rgid in clipboard.rgroups) {
|
|
if (clipboard.rgroups.hasOwnProperty(rgid) && !restruct.molecule.rgroups.has(rgid))
|
|
rgnew.push(rgid);
|
|
}
|
|
|
|
// assign fragments to r-groups
|
|
for (var frid in clipboard.rgmap) {
|
|
if (clipboard.rgmap.hasOwnProperty(frid))
|
|
action.addOp(new op.RGroupFragment(clipboard.rgmap[frid], fmap[frid]).perform(restruct));
|
|
}
|
|
|
|
for (var i = 0; i < rgnew.length; ++i)
|
|
action.mergeWith(fromRGroupAttrs(restruct, rgnew[i], clipboard.rgroups[rgnew[i]]));
|
|
|
|
// bonds
|
|
for (var bid = 0; bid < clipboard.bonds.length; bid++) {
|
|
var bond = Object.assign({}, clipboard.bonds[bid]);
|
|
action.addOp(new op.BondAdd(amap[bond.begin], amap[bond.end], bond).perform(restruct));
|
|
}
|
|
// sgroups
|
|
for (var sgid = 0; sgid < clipboard.sgroups.length; sgid++) {
|
|
var sgroupInfo = clipboard.sgroups[sgid];
|
|
var atoms = sgroupInfo.atoms;
|
|
var sgatoms = [];
|
|
for (var sgaid = 0; sgaid < atoms.length; sgaid++)
|
|
sgatoms.push(amap[atoms[sgaid]]);
|
|
var newsgid = restruct.molecule.sgroups.newId();
|
|
var sgaction = fromSgroupAddition(restruct, sgroupInfo.type, sgatoms, sgroupInfo.attrs, newsgid, sgroupInfo.pp ? sgroupInfo.pp.add(offset) : null);
|
|
for (var iop = sgaction.operations.length - 1; iop >= 0; iop--)
|
|
action.addOp(sgaction.operations[iop]);
|
|
}
|
|
// reaction arrows
|
|
if (restruct.rxnArrows.count() < 1) {
|
|
for (var raid = 0; raid < clipboard.rxnArrows.length; raid++)
|
|
action.addOp(new op.RxnArrowAdd(clipboard.rxnArrows[raid].pp.add(offset)).perform(restruct));
|
|
}
|
|
// reaction pluses
|
|
for (var rpid = 0; rpid < clipboard.rxnPluses.length; rpid++)
|
|
action.addOp(new op.RxnPlusAdd(clipboard.rxnPluses[rpid].pp.add(offset)).perform(restruct));
|
|
// chiral flag
|
|
if (pstruct.isChiral) {
|
|
var bb = pstruct.getCoordBoundingBox();
|
|
var pp = new Vec2(bb.max.x, bb.min.y - 1);
|
|
action.mergeWith(fromChiralFlagAddition(restruct, pp.add(offset)));
|
|
}
|
|
// thats all
|
|
action.operations.reverse();
|
|
return action;
|
|
}
|
|
|
|
function fromFlip(restruct, selection, dir) { // eslint-disable-line max-statements
|
|
const struct = restruct.molecule;
|
|
|
|
const action = new Action();
|
|
|
|
if (!selection)
|
|
selection = structSelection(struct);
|
|
|
|
if (!selection.atoms)
|
|
return action.perform(restruct);
|
|
|
|
const fids = selection.atoms.reduce((acc, aid) => {
|
|
const atom = struct.atoms.get(aid);
|
|
|
|
if (!acc[atom.fragment])
|
|
acc[atom.fragment] = [];
|
|
|
|
acc[atom.fragment].push(aid);
|
|
return acc;
|
|
}, {});
|
|
|
|
const isFragFound = Object.keys(fids).find(frag => {
|
|
frag = parseInt(frag, 10);
|
|
return !Set.eq(struct.getFragmentIds(frag), Set.fromList(fids[frag]));
|
|
});
|
|
|
|
if (isFragFound)
|
|
return action; // empty action
|
|
|
|
Object.keys(fids).forEach(frag => {
|
|
const fragment = Set.fromList(fids[frag]);
|
|
|
|
const bbox = struct.getCoordBoundingBox(fragment);
|
|
|
|
Set.each(fragment, aid => {
|
|
const atom = struct.atoms.get(aid);
|
|
const d = new Vec2();
|
|
|
|
/* eslint-disable no-mixed-operators*/
|
|
if (dir === 'horizontal')
|
|
d.x = bbox.min.x + bbox.max.x - 2 * atom.pp.x;
|
|
else // 'vertical'
|
|
d.y = bbox.min.y + bbox.max.y - 2 * atom.pp.y;
|
|
/* eslint-enable no-mixed-operators*/
|
|
|
|
action.addOp(new op.AtomMove(aid, d));
|
|
});
|
|
});
|
|
|
|
if (selection.bonds) {
|
|
selection.bonds.forEach(bid => {
|
|
const bond = struct.bonds.get(bid);
|
|
|
|
if (bond.type !== Struct.Bond.PATTERN.TYPE.SINGLE)
|
|
return;
|
|
|
|
if (bond.stereo === Struct.Bond.PATTERN.STEREO.UP) {
|
|
action.addOp(new op.BondAttr(bid, 'stereo', Struct.Bond.PATTERN.STEREO.DOWN));
|
|
return;
|
|
}
|
|
|
|
if (bond.stereo === Struct.Bond.PATTERN.STEREO.DOWN)
|
|
action.addOp(new op.BondAttr(bid, 'stereo', Struct.Bond.PATTERN.STEREO.UP));
|
|
});
|
|
}
|
|
|
|
return action.perform(restruct);
|
|
}
|
|
|
|
function fromRotate(restruct, selection, pos, angle) { // eslint-disable-line max-statements
|
|
var struct = restruct.molecule;
|
|
|
|
var action = new Action();
|
|
if (!selection)
|
|
selection = structSelection(struct);
|
|
|
|
if (selection.atoms) {
|
|
selection.atoms.forEach(function (aid) {
|
|
var atom = struct.atoms.get(aid);
|
|
action.addOp(new op.AtomMove(aid, rotateDelta(atom.pp, pos, angle)));
|
|
});
|
|
}
|
|
|
|
if (selection.rxnArrows) {
|
|
selection.rxnArrows.forEach(function (aid) {
|
|
var arrow = struct.rxnArrows.get(aid);
|
|
action.addOp(new op.RxnArrowMove(aid, rotateDelta(arrow.pp, pos, angle)));
|
|
});
|
|
}
|
|
|
|
if (selection.rxnPluses) {
|
|
selection.rxnPluses.forEach(function (pid) {
|
|
var plus = struct.rxnPluses.get(pid);
|
|
action.addOp(new op.RxnPlusMove(pid, rotateDelta(plus.pp, pos, angle)));
|
|
});
|
|
}
|
|
|
|
if (selection.sgroupData) {
|
|
selection.sgroupData.forEach(function (did) {
|
|
var data = struct.sgroups.get(did);
|
|
action.addOp(new op.SGroupDataMove(did, rotateDelta(data.pp, pos, angle)));
|
|
});
|
|
}
|
|
|
|
if (selection.chiralFlags) {
|
|
selection.chiralFlags.forEach(function (fid) {
|
|
var flag = restruct.chiralFlags.get(fid);
|
|
action.addOp(new op.ChiralFlagMove(rotateDelta(flag.pp, pos, angle)));
|
|
});
|
|
}
|
|
|
|
return action.perform(restruct);
|
|
}
|
|
|
|
function fromBondAlign(restruct, bid, dir) {
|
|
var struct = restruct.molecule;
|
|
var bond = struct.bonds.get(bid);
|
|
var begin = struct.atoms.get(bond.begin);
|
|
var end = struct.atoms.get(bond.end);
|
|
|
|
var center = begin.pp.add(end.pp).scaled(0.5);
|
|
var angle = utils.calcAngle(begin.pp, end.pp);
|
|
var atoms = getFragmentAtoms(struct, begin.fragment);
|
|
angle = (dir === 'horizontal') ? -angle : ((Math.PI / 2) - angle);
|
|
|
|
// TODO: choose minimal angle
|
|
// console.info('single bond', utils.degrees(angle), atoms, dir);
|
|
return fromRotate(restruct, { atoms: atoms }, center, angle);
|
|
}
|
|
|
|
function structSelection(struct) {
|
|
return ['atoms', 'bonds', 'frags', 'sgroups', 'rgroups', 'rxnArrows', 'rxnPluses'].reduce(function (res, key) {
|
|
res[key] = struct[key].keys();
|
|
return res;
|
|
}, {});
|
|
}
|
|
|
|
function getFragmentAtoms(struct, frid) {
|
|
var atoms = [];
|
|
struct.atoms.each(function (aid, atom) {
|
|
if (atom.fragment == frid)
|
|
atoms.push(aid);
|
|
});
|
|
return atoms;
|
|
}
|
|
|
|
function rotateDelta(v, pos, angle) {
|
|
var v1 = v.sub(pos);
|
|
v1 = v1.rotate(angle);
|
|
v1.add_(pos); // eslint-disable-line no-underscore-dangle
|
|
return v1.sub(v);
|
|
}
|
|
|
|
function atomGetAttr(restruct, aid, name) {
|
|
return restruct.molecule.atoms.get(aid)[name];
|
|
}
|
|
|
|
function atomGetDegree(restruct, aid) {
|
|
return restruct.atoms.get(aid).a.neighbors.length;
|
|
}
|
|
|
|
function atomGetNeighbors(restruct, aid) {
|
|
var atom = restruct.atoms.get(aid);
|
|
var neiAtoms = [];
|
|
for (var i = 0; i < atom.a.neighbors.length; ++i) {
|
|
var hb = restruct.molecule.halfBonds.get(atom.a.neighbors[i]);
|
|
neiAtoms.push({
|
|
aid: hb.end - 0,
|
|
bid: hb.bid - 0
|
|
});
|
|
}
|
|
return neiAtoms;
|
|
}
|
|
|
|
function atomGetSGroups(restruct, aid) {
|
|
var atom = restruct.atoms.get(aid);
|
|
return Set.list(atom.a.sgs);
|
|
}
|
|
|
|
function atomGetPos(restruct, id) {
|
|
return restruct.molecule.atoms.get(id).pp;
|
|
}
|
|
|
|
function fromAtomAction(restruct, newSg, sourceAtoms) {
|
|
return sourceAtoms.reduce(function (acc, atom) {
|
|
acc.action = acc.action.mergeWith(
|
|
fromSeveralSgroupAddition(restruct, newSg.type, [atom], newSg.attrs)
|
|
);
|
|
return acc;
|
|
}, {
|
|
action: new Action(),
|
|
selection: {
|
|
atoms: sourceAtoms,
|
|
bonds: []
|
|
}
|
|
});
|
|
}
|
|
|
|
function fromGroupAction(restruct, newSg, sourceAtoms, targetAtoms) {
|
|
const fragIds = Object.keys(
|
|
sourceAtoms.reduce((acc, aid) => {
|
|
const fragId = restruct.atoms.get(aid).a.fragment;
|
|
acc[fragId] = true;
|
|
return acc;
|
|
}, {})
|
|
)
|
|
.map(Number);
|
|
|
|
return fragIds.reduce((acc, fragId) => {
|
|
const atoms = targetAtoms
|
|
.filter(aid => {
|
|
const atom = restruct.atoms.get(aid).a;
|
|
return fragId === atom.fragment;
|
|
})
|
|
.map(Number);
|
|
|
|
const bonds = getAtomsBondIds(restruct.molecule, atoms);
|
|
|
|
acc.action = acc.action.mergeWith(
|
|
fromSeveralSgroupAddition(restruct, newSg.type, atoms, newSg.attrs)
|
|
);
|
|
|
|
acc.selection.atoms = acc.selection.atoms.concat(atoms);
|
|
acc.selection.bonds = acc.selection.bonds.concat(bonds);
|
|
|
|
return acc;
|
|
}, {
|
|
action: new Action(),
|
|
selection: {
|
|
atoms: [],
|
|
bonds: []
|
|
}
|
|
});
|
|
}
|
|
|
|
function fromBondAction(restruct, newSg, sourceAtoms, currSelection) {
|
|
var struct = restruct.molecule;
|
|
var bonds = getAtomsBondIds(struct, sourceAtoms);
|
|
|
|
if (currSelection.bonds)
|
|
bonds = uniq(bonds.concat(currSelection.bonds));
|
|
|
|
return bonds.reduce(function (acc, bondid) {
|
|
var bond = struct.bonds.get(bondid);
|
|
|
|
acc.action = acc.action
|
|
.mergeWith(
|
|
fromSeveralSgroupAddition(restruct, newSg.type, [bond.begin, bond.end], newSg.attrs)
|
|
);
|
|
acc.selection.bonds.push(bondid);
|
|
|
|
return acc;
|
|
}, {
|
|
action: new Action(),
|
|
selection: {
|
|
atoms: sourceAtoms,
|
|
bonds: []
|
|
}
|
|
});
|
|
}
|
|
|
|
function fromMultiFragmentAction(restruct, newSg, atoms) {
|
|
const bonds = getAtomsBondIds(restruct.molecule, atoms);
|
|
return {
|
|
action: fromSeveralSgroupAddition(restruct, newSg.type, atoms, newSg.attrs),
|
|
selection: {
|
|
atoms,
|
|
bonds
|
|
}
|
|
};
|
|
}
|
|
|
|
function fromDescriptorsAlign(restruct) {
|
|
const action = new Action();
|
|
action.addOp(new op.AlignDescriptors(restruct));
|
|
return action.perform(restruct);
|
|
}
|
|
|
|
function getAtomsBondIds(struct, atoms) {
|
|
return struct.bonds.keys()
|
|
.reduce(function (acc, bondid) {
|
|
var bond = struct.bonds.get(bondid);
|
|
|
|
if (atoms.includes(bond.begin) && atoms.includes(bond.end))
|
|
acc.push(parseInt(bondid));
|
|
|
|
return acc;
|
|
}, []);
|
|
}
|
|
|
|
module.exports = Object.assign(Action, {
|
|
fromMultipleMove: fromMultipleMove,
|
|
fromAtomAddition: fromAtomAddition,
|
|
fromArrowAddition: fromArrowAddition,
|
|
fromArrowDeletion: fromArrowDeletion,
|
|
fromChiralFlagAddition: fromChiralFlagAddition,
|
|
fromChiralFlagDeletion: fromChiralFlagDeletion,
|
|
fromPlusAddition: fromPlusAddition,
|
|
fromPlusDeletion: fromPlusDeletion,
|
|
fromAtomDeletion: fromAtomDeletion,
|
|
fromBondDeletion: fromBondDeletion,
|
|
fromFragmentDeletion: fromFragmentDeletion,
|
|
fromAtomMerge: fromAtomMerge,
|
|
fromBondFlipping: fromBondFlipping,
|
|
fromTemplateOnCanvas: fromTemplateOnCanvas,
|
|
fromTemplateOnAtom: fromTemplateOnAtom,
|
|
fromTemplateOnBond: fromTemplateOnBond,
|
|
fromAtomsAttrs: fromAtomsAttrs,
|
|
fromBondAttrs: fromBondAttrs,
|
|
fromChain: fromChain,
|
|
fromBondAddition: fromBondAddition,
|
|
fromNewCanvas: fromNewCanvas,
|
|
fromSgroupType: fromSgroupType,
|
|
fromSgroupDeletion: fromSgroupDeletion,
|
|
fromSgroupAttrs: fromSgroupAttrs,
|
|
fromRGroupFragment: fromRGroupFragment,
|
|
fromPaste: fromPaste,
|
|
fromRGroupAttrs: fromRGroupAttrs,
|
|
fromSgroupAddition: fromSgroupAddition,
|
|
fromFlip: fromFlip,
|
|
fromRotate: fromRotate,
|
|
fromBondAlign: fromBondAlign,
|
|
fromAtomAction: fromAtomAction,
|
|
fromGroupAction: fromGroupAction,
|
|
fromBondAction: fromBondAction,
|
|
fromSeveralSgroupAddition: fromSeveralSgroupAddition,
|
|
fromUpdateIfThen: fromUpdateIfThen,
|
|
fromMultiFragmentAction: fromMultiFragmentAction,
|
|
fromDescriptorsAlign: fromDescriptorsAlign
|
|
});
|