org.xmlcml.cml.tools.BondTool Maven / Gradle / Ivy
/**
* Copyright 2011 Peter Murray-Rust et. al.
*
* 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.
*/
package org.xmlcml.cml.tools;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import nu.xom.Attribute;
import org.apache.log4j.Logger;
import org.xmlcml.cml.base.AbstractTool;
import org.xmlcml.cml.base.CMLConstants;
import org.xmlcml.cml.base.CMLElement.CoordinateType;
import org.xmlcml.cml.element.CMLAtom;
import org.xmlcml.cml.element.CMLAtomSet;
import org.xmlcml.cml.element.CMLBond;
import org.xmlcml.cml.element.CMLBondStereo;
import org.xmlcml.cml.element.CMLMolecule;
import org.xmlcml.cml.graphics.CMLDrawable;
import org.xmlcml.cml.graphics.SVGElement;
import org.xmlcml.cml.graphics.SVGG;
import org.xmlcml.cml.graphics.SVGLine;
import org.xmlcml.cml.graphics.SVGPath;
import org.xmlcml.euclid.Point3;
import org.xmlcml.euclid.Real2;
import org.xmlcml.euclid.Real2Array;
import org.xmlcml.euclid.Transform2;
import org.xmlcml.molutil.ChemicalElement.AS;
/**
* additional tools for bond. not fully developed
*
* @author pmr
*
*/
public class BondTool extends AbstractSVGTool {
@SuppressWarnings("unused")
private static Logger LOG = Logger.getLogger(BondTool.class);
private CMLBond bond;
private CMLMolecule molecule;
private BondDisplay bondDisplay;
private MoleculeTool moleculeTool;
private MoleculeDisplay moleculeDisplay;
private double width = 1.0;
private double widthFactor;
private List groupToolList;
/**
* constructor
*
* @param bond
* @deprecated use getOrCreateTool
*/
public BondTool(CMLBond bond) {
if (bond == null) {
throw new RuntimeException("null bond");
}
this.bond = bond;
setDefaults();
molecule = bond.getMolecule();
if (molecule == null) {
throw new RuntimeException("Bond must be in molecule");
}
}
private void setDefaults() {
// this.ensureBondDisplay();
}
/** gets BondTool associated with bond.
* if null creates one and sets it in bond
* @param bond
* @return tool
*/
public static BondTool getOrCreateTool(CMLBond bond) {
BondTool bondTool = (BondTool) bond.getTool();
if (bondTool == null) {
bondTool = new BondTool(bond);
bond.setTool(bondTool);
}
return bondTool;
}
public CMLBond getBond() {
return bond;
}
public CMLMolecule getMolecule() {
return molecule;
}
/** get conventional bond order.
* if order is given as unknown returns 0.5
* if order is absent null or otherwise unknown returns 0.0
* @return 1.0, 2.0, 3.0 for S,D,T; 1.5 for A, else 0.0
*/
public static double getNumericOrder(String order) {
double orderD = 0.0;
if (order == null) {
//
} else if (CMLBond.isDouble(order)) {
orderD = 2.0;
} else if (CMLBond.isTriple(order)) {
orderD = 3.0;
} else if (CMLBond.isSingle(order)) {
orderD = 1.0;
} else if (order.equals(CMLBond.AROMATIC)) {
orderD = 1.5;
} else if (order.equals(CMLBond.UNKNOWN_ORDER)) {
orderD = 0.5;
} else if (order.equals(CMLBond.ZERO)) {
orderD = 0.0;
} else {
}
return orderD;
}
/** get conventional bond order.
* if order is given as unknown returns 0.5
* if order is absent null or otherwise unknown returns 0.0
* @return 1.0, 2.0, 3.0 for S,D,T; 1.5 for A, else 0.0
*/
public double getNumericOrder() {
return getNumericOrder(bond.getOrder());
}
/**
*
* @param reaction
* @return
*/
public static AbstractSVGTool getOrCreateSVGTool(CMLBond bond) {
return (AbstractSVGTool) BondTool.getOrCreateTool(bond);
}
/** create a table to lookup bonds by atom Ids.
*
* use lookupBond(Map, atom, atom) to retrieve
*
* Map bondMap = BondToolImpl.createLookupTableByAtomIds();
* ...
* CMLBond bond = BondToolImpl.lookupBondMap(bondMap, atom1, atom2);
*
* @param bonds array of bonds to index by atom IDs
* @return Map indexed on atom IDs
*/
@SuppressWarnings("all")
public static Map createLookupTableByAtomIds(List bonds) {
Map map = new HashMap();
for (CMLBond bond : bonds) {
map.put(CMLBond.atomHash(bond), bond);
}
return map;
}
/** returns a "g" element
* this contains the lines for bond
* @param drawable
* @return null if problem or atom has no coords
*/
public SVGElement createGraphicsElement(CMLDrawable drawable) {
g = null;
List atoms = bond.getAtoms();
Real2 xy0 = atoms.get(0).getXY2();
Real2 xy1 = atoms.get(1).getXY2();
if (xy0 == null) {
// no coordinates
} else if (xy1 == null) {
// no coordinates
} else {
g = (drawable == null) ? new SVGG() : drawable.createGraphicsElement();
g.setUserElement(bond);
double bondWidth = (bondDisplay == null) ? 1.0 : bondDisplay.getScaledWidth();
double middleFactor = (bondDisplay == null) ? 1.0 : bondDisplay.getDoubleMiddleFactor();
String order = bond.getOrder();
CMLBondStereo bondStereo = bond.getBondStereo();
String bondStereoS = (bondStereo == null) ? null : bondStereo.getXMLContent();
// highlight
// FIXME obsolete?
SelectionTool selectionTool = (moleculeTool == null) ? null : moleculeTool.getSelectionTool();
if (selectionTool != null) {
if (selectionTool.isSelected(bond)) {
double factor = 3.0;
if (order.equals(CMLBond.DOUBLE_D)) {
factor = 5.0;
} else if (order.equals(CMLBond.TRIPLE_T)) {
factor = 7.0;
}
SVGElement line = createBond("yellow", bondWidth*factor, xy0, xy1);
g.appendChild(line);
line.setFill("yellow");
line.setOpacity(0.40);
}
}
if (false) {
} else if (CMLBond.WEDGE.equals(bondStereoS) ||
CMLBond.HATCH.equals(bondStereoS)) {
SVGPath path = createWedgeHatch("black", bondDisplay.getHatchCount(),
bondWidth, xy0, xy1, bondStereoS);
g.appendChild(path);
} else if (order == null || CMLBond.isSingle(order)) {
g.appendChild(createBond("black", bondWidth, xy0, xy1));
} else if (order.equals(CMLBond.AROMATIC)) {
SVGElement line = createBond("black", bondWidth, xy0, xy1);
line.addDashedStyle(bondWidth);
g.appendChild(line);
} else if (CMLBond.isDouble(order)) {
g.appendChild(createBond("black", 2.55*bondWidth, xy0, xy1));
g.appendChild(createBond("white",
middleFactor*0.85*bondWidth, xy0, xy1));
} else if (CMLBond.isTriple(order)) {
g.appendChild(createBond("black", 3.75*bondWidth, xy0, xy1));
g.appendChild(createBond("white", 2.25*bondWidth, xy0, xy1));
g.appendChild(createBond("black", 0.75*bondWidth, xy0, xy1));
} else {
g.appendChild(createBond("white", bondWidth, xy0, xy1));
}
}
return g;
}
private SVGElement createBond(String stroke, double width, Real2 xy0, Real2 xy1) {
SVGElement line = new SVGLine(xy0, xy1);
line.setStroke(stroke);
line.setStrokeWidth(width);
line.setUserElement(bond);
line.addAttribute(new Attribute("id", bond.getId()));
return line;
}
private SVGPath createWedgeHatch(String fill, int nhatch, double width, Real2 xy0, Real2 xy1, String bondStereoS) {
Real2Array array = new Real2Array();
array.add(xy0);
double wf = bondDisplay.getWedgeFactor() * 0.5;
Real2 v1 = xy1.subtract(xy0);
// rotate by PI/2
Real2 v2 = new Real2(
v1.getY() * wf,
v1.getX() * (-wf)
);
SVGPath path = null;
if (bondStereoS.equals(CMLBond.WEDGE)) {
Real2 xy1a = xy1.plus(v2);
array.add(xy1a);
Real2 xy1b = xy1.subtract(v2);
array.add(xy1b);
path = new SVGPath(array);
path.setFill(fill);
} else {
v1 = v1.multiplyBy(1./(double) nhatch);
v2 = v2.multiplyBy(1./(double) nhatch);
Real2 currentMid = xy0;
path = new SVGPath();
path.setStrokeWidth(width);
path.setStroke(fill);
path.setStrokeWidth(width);
StringBuilder sb = new StringBuilder();
for (int i = 1; i < nhatch; i++) {
currentMid.plusEquals(v1);
Real2 v22 = v2.multiplyBy((double) i);
Real2 xyplus = currentMid.plus(v22);
sb.append("M");
sb.append(xyplus.getX()+" ");
sb.append(xyplus.getY()+" ");
Real2 xyminus = currentMid.subtract(v22);
sb.append("L");
sb.append(xyminus.getX()+" ");
sb.append(xyminus.getY()+" ");
}
path.setDString(sb.toString());
}
return path;
}
/**
* @return the bondDisplay
*/
public BondDisplay getBondDisplay() {
return bondDisplay;
}
/**
* @param bondDisplay the bondDisplay to set
*/
public void setBondDisplay(BondDisplay bondDisplay) {
this.bondDisplay = bondDisplay;
}
public void ensureId() {
String id = bond.getId();
if (id == null) {
bond.setId(bond.getAtomId(0)+CMLConstants.S_UNDER+bond.getAtomId(1));
}
}
public void ensureBondDisplay() {
if (bondDisplay == null) {
ensureMoleculeDisplay();
if (moleculeDisplay != null) {
BondDisplay bondDisplayx = moleculeDisplay.getDefaultBondDisplay();
if (bondDisplayx != null) {
bondDisplay = new BondDisplay(bondDisplayx);
}
}
}
}
public void ensureMoleculeDisplay() {
if (moleculeDisplay == null) {
ensureMoleculeTool();
moleculeDisplay = moleculeTool.getMoleculeDisplay();
}
}
private void ensureMoleculeTool() {
if (moleculeTool == null) {
CMLMolecule molecule = bond.getMolecule();
moleculeTool = MoleculeTool.getOrCreateTool(molecule);
}
}
/**
* @param atomDisplay
* @return true =>omit
*/
public boolean omitFromDisplay(AtomDisplay atomDisplay) {
boolean omit = false;
if (atomDisplay != null &&
atomDisplay.isOmitHydrogens()) {
List atoms = bond.getAtoms();
if (atomDisplay.omitAtom(atoms.get(0)) ||
atomDisplay.omitAtom(atoms.get(1))) {
omit = true;
}
}
return omit;
}
/**
* @return the moleculeTool
*/
public AbstractTool getMoleculeTool() {
return moleculeTool;
}
/**
* @param moleculeTool the moleculeTool to set
*/
public void setMoleculeTool(MoleculeTool moleculeTool) {
this.moleculeTool = moleculeTool;
}
/**
* @return the width
*/
public double getWidth() {
return width;
}
/**
* @param width the width to set
*/
public void setWidth(double width) {
this.width = width;
}
/**
* @return the widthFactor
*/
public double getWidthFactor() {
return widthFactor;
}
/**
* @param widthFactor the widthFactor to set
*/
public void setWidthFactor(double widthFactor) {
this.widthFactor = widthFactor;
}
/**
* gets atoms on one side of bond. only applicable to acyclic bonds
* or spiro systems (if allowSpiro==true)
* if bond is cyclic, whole molecule will be returned unless
* bond is in spiro system and allowSpiro is set
*
* returns atom and all
* descendant atoms.
*
* @param moleculeTool TODO
* @param atom defining side of bond
* @throws RuntimeException atom is not in bond
* @return atomSet of downstream atoms
*/
public CMLAtomSet getDownstreamAtoms(CMLAtom atom, CMLAtomSet stopSet) {
CMLAtomSet atomSet = new CMLAtomSet();
CMLAtom otherAtom = bond.getOtherAtom(atom);
if (otherAtom != null) {
atomSet = AtomTool.getOrCreateTool(otherAtom).getDownstreamAtoms(atom, stopSet);
}
return atomSet;
}
/**
* gets atoms on one side of bond. only applicable to acyclic bonds if bond
* is cyclic, whole molecule will be returned! returns atom and all
* descendant atoms.
* calls getDownstreamAtoms(atom, stopSet = null)
*
* @param atom defining side of bond
* @throws RuntimeException atom is not in bond
* @return atomSet of downstream atoms
*/
public CMLAtomSet getDownstreamAtoms(CMLAtom atom) {
return getDownstreamAtoms(atom, null);
}
/**
* transform describing the rotation and stretching of a bond.
*
Transform2 t = this.getTranformToRotateAndStretchBond(movingAtom, targetPoint) {
* The movingAtom is translated;
* the fixedAtom is found by:
CMLAtom pivotAtom = bond.getOtherAtom(movingAtom);
A typical use is the dragging of an atom in an acyclic bond
this carries all the downstream atoms:
CMLAtomSet atomSet = this.getDownstreamAtoms(pivotAtom);
atomSet.transform(t);
* @param movingAtom
* @param targetPoint point to translate mvingAtom to
* @return
*/
public Transform2 getTransformToRotateAndStretchBond(CMLAtom movingAtom, Real2 targetPoint) {
CMLAtom pivotAtom = bond.getOtherAtom(movingAtom);
Real2 pivotPoint = pivotAtom.getXY2();
Real2 movingPoint = movingAtom.getXY2();
Transform2 t = Transform2.getTransformToRotateAndStretchLine(pivotPoint, movingPoint, targetPoint);
return t;
}
/**
* gets four atoms defining cis/trans isomerism.
*
* selects 4 atoms lig0-atom0-atom1-lig1, where atom0 and atom1 are the
* atoms in the bond and lig0 and lig1 are atoms bonded to each end
* respectively Returns null unless atom0 and atom 1 each have 1 or 2
* ligands (besides themselves) if atom0 and/or atom1 have 2 ligands the
* selection is deterministic but arbitrary - hydrogens are not used if
* possible - normally the first Id in lexical order (i.e. no implied
* semantics). Exceptions are thrown if ligands are linear with atom0-atom1
* or 2 ligands on same atom are on the same side these may be normal
* exceptions (e.g. in a C=O bond) so should be caught and analysed rather
* than dumping the program
*
* @param bond
* @exception RuntimeException
* 2 ligand atoms on same atom on same side too few or too
* many ligands at either end (any) ligand is linear with
* bond
* @return the four atoms
*/
static CMLAtom[] createAtomRefs4(CMLBond bond) throws RuntimeException {
CMLAtom[] atom4 = null;
List atomList = bond.getAtoms();
List ligands0 = AtomTool.getSubstituentLigandList(bond, atomList.get(0));
List ligands1 = AtomTool.getSubstituentLigandList(bond, atomList.get(1));
if (ligands0.size() == 0) {
// no ligands on atom
} else if (ligands1.size() == 0) {
// no ligands on atom
} else if (ligands0.size() > 2) {
throw new RuntimeException("Too many ligands on atom: "
+ atomList.get(0).getId());
} else if (ligands1.size() > 2) {
throw new RuntimeException("Too many ligands on atom: "
+ atomList.get(1).getId());
} else {
CMLAtom ligand0 = ligands0.get(0);
if (AS.H.equals(ligand0.getElementType()) && ligands0.size() > 1
&& ligands0.get(1) != null) {
ligand0 = ligands0.get(1);
} else if (ligands0.size() > 1
&& ligands0.get(1).getId().compareTo(atomList.get(0).getId()) < 0) {
ligand0 = ligands0.get(1);
}
CMLAtom ligand1 = ligands1.get(0);
if (AS.H.equals(ligand1.getElementType()) && ligands1.size() > 1
&& ligands1.get(1) != null) {
ligand1 = ligands1.get(1);
} else if (ligands1.size() > 1
&& ligands1.get(1).getId().compareTo(atomList.get(1).getId()) < 0) {
ligand1 = ligands1.get(1);
}
atom4 = new CMLAtom[4];
atom4[0] = ligand0;
atom4[1] = atomList.get(0);
atom4[2] = atomList.get(1);
atom4[3] = ligand1;
}
return atom4;
}
/** get 2D mid point of bond.
*
* @return null if either XY2 coordinate is missing
*/
public Real2 getMidPoint2D() {
List atoms = bond.getAtoms();
Real2 xy0 = atoms.get(0).getXY2();
Real2 xy1 = atoms.get(1).getXY2();
return (xy0 == null || xy1 == null) ? null :
xy0.getMidPoint(xy1);
}
/** get 3D mid point of bond.
*
* @return null if either XYZ3 coordinate is missing
*/
public Point3 getMidPoint3D() {
List atoms = bond.getAtoms();
Point3 xyz0 = atoms.get(0).getXYZ3();
Point3 xyz1 = atoms.get(1).getXYZ3();
return (xyz0 == null || xyz1 == null) ? null :
xyz0.getMidPoint(xyz1);
}
public MoleculeDisplay getMoleculeDisplay() {
return moleculeDisplay;
}
public void setMoleculeDisplay(MoleculeDisplay moleculeDisplay) {
this.moleculeDisplay = moleculeDisplay;
}
public boolean hasCoordinates(CoordinateType type) {
List atoms = bond.getAtoms();
return (atoms != null && atoms.size() == 2 &&
atoms.get(0).hasCoordinates(type) &&
atoms.get(1).hasCoordinates(type));
}
public String getDownstreamMorganString(CMLAtom atom) {
CMLAtomSet atomSet = this.getDownstreamAtoms(atom);
Morgan morgan = new Morgan(atomSet);
String s = morgan.getEquivalenceString();
return s;
}
public boolean matchesGroupAgainstSMILES(int zeroOrOne, String groupSMILESString) {
CMLAtom atom = bond.getAtom(zeroOrOne);
if (atom == null) {
throw new RuntimeException("Bad atom serial in bond: "+zeroOrOne);
}
String groupMorganString = Morgan.createMorganStringFromSMILESRGroup(groupSMILESString);
return this.getDownstreamMorganString(atom).equals(groupMorganString);
}
public void addGroupTool(GroupTool groupToolNew) {
ensureGroupToolList();
boolean duplicate = false;
for (GroupTool groupTool : groupToolList) {
if (groupTool.isDuplicate(groupToolNew)) {
duplicate = true;
break;
}
}
if (!duplicate) {
groupToolList.add(groupToolNew);
}
}
public List getGroupToolList() {
return groupToolList;
}
private void ensureGroupToolList() {
if (this.groupToolList == null) {
this.groupToolList = new ArrayList();
}
}
public void deleteDownstreamAtoms(CMLAtom rootAtom, CMLAtom otherAtom) {
CMLAtomSet atomSet = this.getDownstreamAtoms(rootAtom);
atomSet.removeAtom(otherAtom);
atomSet.removeAtom(rootAtom);
for (CMLAtom atom : atomSet.getAtoms()) {
CMLMolecule molecule = atom.getMolecule();
molecule.deleteAtom(atom);
}
}
public int getElectronCount() {
return (int) (2 * this.getNumericOrder()+0.01);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy