Skip to content

Commit ba402e8

Browse files
committed
Add read support for stereo groups to MOLfile V2000 and V3000 as well as CXSMILES.
1 parent c1e2290 commit ba402e8

File tree

9 files changed

+789
-96
lines changed

9 files changed

+789
-96
lines changed

display/renderbasic/src/main/java/org/openscience/cdk/renderer/generators/standard/StandardGenerator.java

Lines changed: 105 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@
2525
package org.openscience.cdk.renderer.generators.standard;
2626

2727
import org.openscience.cdk.CDKConstants;
28-
import org.openscience.cdk.geometry.GeometryUtil;
2928
import org.openscience.cdk.interfaces.IAtom;
3029
import org.openscience.cdk.interfaces.IAtomContainer;
3130
import org.openscience.cdk.interfaces.IBond;
3231
import org.openscience.cdk.interfaces.IChemObject;
3332
import org.openscience.cdk.interfaces.IChemObjectBuilder;
3433
import org.openscience.cdk.interfaces.IPseudoAtom;
34+
import org.openscience.cdk.interfaces.IStereoElement;
3535
import org.openscience.cdk.renderer.RendererModel;
3636
import org.openscience.cdk.renderer.SymbolVisibility;
3737
import org.openscience.cdk.renderer.color.IAtomColorer;
@@ -52,9 +52,7 @@
5252

5353
import javax.vecmath.Point2d;
5454
import javax.vecmath.Vector2d;
55-
import java.awt.Color;
56-
import java.awt.Font;
57-
import java.awt.Shape;
55+
import java.awt.*;
5856
import java.awt.geom.Area;
5957
import java.awt.geom.Ellipse2D;
6058
import java.awt.geom.Point2D;
@@ -72,16 +70,16 @@
7270
* diagram. These are generated together allowing the bonds to drawn cleanly without overlap. The
7371
* generate is heavily based on ideas documented in {@cdk.cite Brecher08} and {@cdk.cite Clark13}.
7472
*
75-
*
73+
*
7674
*
7775
* Atom symbols are provided as {@link GeneralPath} outlines. This allows the depiction to be
7876
* independent of the system used to view the diagram (primarily important for vector graphic
79-
* depictions). The font used to generate the diagram must be provided to the constructor.
77+
* depictions). The font used to generate the diagram must be provided to the constructor.
8078
*
8179
* Atoms and bonds can be highlighted by setting the {@link #HIGHLIGHT_COLOR}. The style of
8280
* highlight is set with the {@link Highlighting} parameter.
8381
*
84-
*
82+
*
8583
*
8684
* The <a href="https://github.com/cdk/cdk/wiki/Standard-Generator">Standard Generator - CDK Wiki
8785
* page</a> provides extended details of using and configuring this generator.
@@ -221,6 +219,12 @@ public IRenderingElement generate(IAtomContainer container, RendererModel parame
221219
stroke);
222220
IRenderingElement donuts = donutGenerator.generate();
223221

222+
String enantiomerText = determineEnantiomerText(container);
223+
if (enantiomerText == null) {
224+
// no global 'enantiomer text' add them per atom if exists
225+
addStereoGroupAnnotations(container);
226+
}
227+
224228
AtomSymbol[] symbols = generateAtomSymbols(container, symbolRemap,
225229
visibility, parameters,
226230
annotations, foreground,
@@ -308,31 +312,31 @@ public IRenderingElement generate(IAtomContainer container, RendererModel parame
308312
middleLayer.add(MarkedElement.markupAtom(symbolElements, atom));
309313
}
310314
}
311-
315+
312316
if (style == HighlightStyle.OuterGlowWhiteEdge) {
313317
for (int i = 0; i < container.getAtomCount(); i++) {
314318
IAtom atom = container.getAtom(i);
315-
319+
316320
if (isHidden(atom))
317321
continue;
318-
322+
319323
Color highlight = getHighlightColor(atom, parameters);
320324
Color color = highlight != null ? highlight : coloring.getAtomColor(atom);
321325

322326
if (symbols[i] == null) {
323327
continue;
324328
}
325-
329+
326330
ElementGroup symbolElements = new ElementGroup();
327331
for (Shape shape : symbols[i].getOutlines()) {
328332
GeneralPath path = GeneralPath.shapeOf(shape, color);
329333
symbolElements.add(path);
330334
}
331-
335+
332336
if (highlight != null && !highlight.equals(Color.WHITE)) {
333337
backLayer.add(MarkedElement.markup(outerGlow(symbolElements, Color.WHITE, 10*stroke, stroke), "outerglow"));
334338
}
335-
}
339+
}
336340
}
337341

338342
// Add the Sgroups display elements to the front layer
@@ -348,9 +352,97 @@ public IRenderingElement generate(IAtomContainer container, RendererModel parame
348352
group.add(middleLayer);
349353
group.add(frontLayer);
350354

355+
// maybe move somewhere else (e.g. annotations)
356+
Bounds bounds = new Bounds();
357+
bounds.add(group);
358+
359+
if (enantiomerText != null) {
360+
TextOutline chiralInfo = new TextOutline(enantiomerText, font).resize(1 / scale, -1 / scale);
361+
if (chiralInfo.getBounds().getWidth() > bounds.width()) {
362+
// position center right
363+
double centerY = (bounds.minY + bounds.maxY) / 2;
364+
chiralInfo = chiralInfo.translate(bounds.maxX - (chiralInfo.getBounds().getMinX() - (10*stroke)),
365+
centerY - (chiralInfo.getBounds().getCenterY()));
366+
} else {
367+
// position bottom right corner
368+
chiralInfo = chiralInfo.translate(bounds.maxX - ((chiralInfo.getBounds().getMaxX() + chiralInfo.getCenter().getX()) / 2),
369+
bounds.minY - (chiralInfo.getBounds().getMaxY() + (5*stroke)));
370+
}
371+
group.add(GeneralPath.shapeOf(chiralInfo.getOutline(), foreground));
372+
}
373+
374+
351375
return MarkedElement.markupMol(group, container);
352376
}
353377

378+
/**
379+
* Adds &1 and or1 to each atom in the those stereo groups.
380+
* @param container the container
381+
*/
382+
private void addStereoGroupAnnotations(IAtomContainer container) {
383+
for (IStereoElement<?,?> se : container.stereoElements()) {
384+
// tetrahedral only
385+
if (se.getConfigClass() == IStereoElement.TH) {
386+
int groupInfo = se.getGroupInfo();
387+
if (groupInfo != 0) {
388+
389+
// generate the label &1, or1
390+
String label = null;
391+
switch ((groupInfo & IStereoElement.GRP_TYPE_MASK)) {
392+
case IStereoElement.GRP_AND:
393+
label = "&";
394+
break;
395+
case IStereoElement.GRP_OR:
396+
label = "or";
397+
break;
398+
case IStereoElement.GRP_ABS:
399+
continue;
400+
}
401+
label += Integer.toString(groupInfo >> IStereoElement.GRP_NUM_SHIFT);
402+
403+
// attach it to the central atom
404+
IAtom focus = (IAtom) se.getFocus();
405+
String annotation = focus.getProperty(StandardGenerator.ANNOTATION_LABEL);
406+
if (annotation == null)
407+
annotation = label;
408+
else
409+
annotation += ";" + label;
410+
focus.setProperty(StandardGenerator.ANNOTATION_LABEL,
411+
annotation);
412+
}
413+
}
414+
}
415+
}
416+
417+
/**
418+
* Determines the text to enantiomer text to display next to the structure, "and enantiomer" for racemic mixtures
419+
* or "or enantiomer" for relative stereochemistry. These are generated if all grouped stereo is in the same group,
420+
* i.e. all &1, all or1, all &2 etc.
421+
*
422+
* @param container the molecule
423+
* @return the text
424+
*/
425+
private String determineEnantiomerText(IAtomContainer container) {
426+
int ref_grp = 0;
427+
for (IStereoElement<?, ?> elem : container.stereoElements()) {
428+
if (elem.getConfigClass() == IStereoElement.TH) {
429+
if (elem.getGroupInfo() == 0)
430+
return null;
431+
if (ref_grp == 0)
432+
ref_grp = elem.getGroupInfo();
433+
else if (ref_grp != elem.getGroupInfo())
434+
return null;
435+
}
436+
}
437+
switch ((ref_grp & IStereoElement.GRP_TYPE_MASK)) {
438+
case IStereoElement.GRP_AND:
439+
return "and enantiomer";
440+
case IStereoElement.GRP_OR:
441+
return "or enantiomer";
442+
}
443+
return null;
444+
}
445+
354446
private Color getColorOfAtom(Map<IAtom, String> symbolRemap, IAtomColorer coloring, Color foreground,
355447
HighlightStyle style, IAtom atom, Color highlight) {
356448
// atom is highlighted...?

storage/ctab/src/main/java/org/openscience/cdk/io/MDLV2000Reader.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@
4343
import org.openscience.cdk.interfaces.IIsotope;
4444
import org.openscience.cdk.interfaces.IPseudoAtom;
4545
import org.openscience.cdk.interfaces.ISingleElectron;
46+
import org.openscience.cdk.interfaces.IStereoElement;
4647
import org.openscience.cdk.interfaces.ITetrahedralChirality.Stereo;
4748
import org.openscience.cdk.io.formats.IResourceFormat;
4849
import org.openscience.cdk.io.formats.MDLV2000Format;
4950
import org.openscience.cdk.io.setting.BooleanIOSetting;
5051
import org.openscience.cdk.io.setting.IOSetting;
5152
import org.openscience.cdk.isomorphism.matchers.Expr;
52-
import org.openscience.cdk.isomorphism.matchers.IQueryAtom;
5353
import org.openscience.cdk.isomorphism.matchers.IQueryAtomContainer;
5454
import org.openscience.cdk.isomorphism.matchers.IQueryBond;
5555
import org.openscience.cdk.isomorphism.matchers.QueryAtom;
@@ -383,6 +383,7 @@ private IAtomContainer readAtomContainer(IAtomContainer molecule) throws CDKExce
383383

384384
int nAtoms = readMolfileInt(line, 0);
385385
int nBonds = readMolfileInt(line, 3);
386+
int chiral = readMolfileInt(line, 13);
386387

387388
final IAtom[] atoms = new IAtom[nAtoms];
388389
final IBond[] bonds = new IBond[nBonds];
@@ -543,6 +544,16 @@ private IAtomContainer readAtomContainer(IAtomContainer molecule) throws CDKExce
543544
}
544545
}
545546

547+
// chiral flag not set which means this molecule is this stereoisomer "and" the enantiomer, mark all
548+
// Tetrahedral stereo as AND1 (&1)
549+
if (chiral == 0) {
550+
for (IStereoElement<?,?> se : outputContainer.stereoElements()) {
551+
if (se.getConfigClass() == IStereoElement.TH) {
552+
se.setGrpConfig(IStereoElement.GRP_AND1);
553+
}
554+
}
555+
}
556+
546557
} catch (CDKException exception) {
547558
String error = "Error while parsing line " + linecount + ": " + line + " -> " + exception.getMessage();
548559
logger.error(error);

0 commit comments

Comments
 (0)