Coverage Report

Created: 2025-07-11 06:55

/src/openbabel/src/depict/depict.cpp
Line
Count
Source (jump to first uncovered line)
1
/**********************************************************************
2
depict.cpp - 2D Depiction of molecules using OBPainter.
3
4
Copyright (C) 2009-2010 by Tim Vandermeersch
5
Some portions Copyright (C) 2009 by Chris Morley
6
7
This file is part of the Open Babel project.
8
For more information, see <http://openbabel.org/>
9
10
This program is free software; you can redistribute it and/or modify
11
it under the terms of the GNU General Public License as published by
12
the Free Software Foundation version 2 of the License.
13
14
This program is distributed in the hope that it will be useful,
15
but WITHOUT ANY WARRANTY; without even the implied warranty of
16
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
GNU General Public License for more details.
18
***********************************************************************/
19
20
#include <openbabel/mol.h>
21
#include <openbabel/bond.h>
22
#include <openbabel/ring.h>
23
#include <openbabel/alias.h>
24
#include <openbabel/generic.h>
25
#include <openbabel/depict/depict.h>
26
#include <openbabel/depict/painter.h>
27
#include <openbabel/elements.h>
28
#include <algorithm> // std::reverse
29
#include <iterator> // std::istream_iterator
30
#include <openbabel/stereo/stereo.h>
31
#include <openbabel/stereo/cistrans.h>
32
#include <openbabel/obiter.h>
33
#include <openbabel/obfunctions.h>
34
35
#include <cmath>
36
37
#include <iostream>
38
using namespace std;
39
40
namespace OpenBabel
41
{
42
43
  enum {
44
    Left,
45
    Right,
46
    Up,
47
    Down
48
  };
49
50
  // defined in mol.cpp
51
  extern bool SortAtomZ(const pair<OBAtom*,double> &a, const pair<OBAtom*,double> &b);
52
53
  class OBDepictPrivate
54
  {
55
    public:
56
0
      OBDepictPrivate() : mol(nullptr), painter(nullptr), bondLength(40.0), penWidth(2.0),
57
0
          bondSpacing(6.0), bondWidth(8.0), fontSize(16), subscriptSize(13),
58
0
          aliasMode(false), bondColor("black"), options(0){}
59
0
      virtual ~OBDepictPrivate(){};
60
61
      virtual void DrawSimpleBond(OBAtom *beginAtom, OBAtom *endAtom, int order, bool crossed_bond=false);
62
      virtual void DrawWedge(OBAtom *beginAtom, OBAtom *endAtom);
63
      virtual void DrawHash(OBAtom *beginAtom, OBAtom *endAtom);
64
      virtual void DrawWobblyBond(OBAtom *beginAtom, OBAtom *endAtom);
65
      virtual void DrawRingBond(OBAtom *beginAtom, OBAtom *endAtom, const vector3 &center, int order);
66
      virtual void DrawAtom(OBAtom* atom);
67
      virtual void DrawAtomLabel(const std::string &label, int alignment, const vector3 &pos);
68
69
      virtual void DrawRing(OBRing *ring, OBBitVec &drawnBonds);
70
      virtual void DrawAromaticRing(OBRing *ring, OBBitVec &drawnBonds);
71
72
      bool HasLabel(OBAtom *atom);
73
      void SetWedgeAndHash(OBMol* mol);
74
75
      OBMol     *mol;
76
      OBPainter *painter;
77
      double     bondLength;
78
      double     penWidth;
79
      double     bondSpacing;
80
      double     bondWidth;
81
    // for z-scaling the opacity
82
      double     zScale;
83
      double     zMin;
84
      //bool       drawTerminalC;
85
      int        fontSize, subscriptSize;
86
      bool       aliasMode;
87
      std::string fontFamily;
88
      OBColor    bondColor;
89
      unsigned   options;
90
  };
91
92
  class OBDepictPrivateBallAndStick : public OBDepictPrivate
93
  {
94
  public:
95
0
        OBDepictPrivateBallAndStick(bool symbolOnBall) : m_symbolOnBall(symbolOnBall){}
96
97
        bool m_symbolOnBall;
98
99
        void DrawSimpleBond(OBAtom *beginAtom, OBAtom *endAtom, int order, bool crossed_bond=false) override;
100
        void DrawWedge(OBAtom *beginAtom, OBAtom *endAtom) override;
101
        void DrawHash(OBAtom *beginAtom, OBAtom *endAtom) override;
102
        void DrawWobblyBond(OBAtom *beginAtom, OBAtom *endAtom) override;
103
        void DrawRingBond(OBAtom *beginAtom, OBAtom *endAtom, const vector3 &center, int order) override;
104
        void DrawAtom(OBAtom *atom) override;
105
        void DrawAtomLabel(const std::string &label, int alignment, const vector3 &pos) override;
106
107
        void DrawRing(OBRing *ring, OBBitVec &drawnBonds) override;
108
        void DrawAromaticRing(OBRing *ring, OBBitVec &drawnBonds) override;
109
110
  private:
111
        void DrawAromaticRingBond(OBAtom *prevAtom, OBAtom *beginAtom, OBAtom *endAtom, OBAtom *nextAtom, const vector3 &center, double dist);
112
        double GetAtomRadius(OBAtom* atom);
113
114
  };
115
116
0
  OBDepict::OBDepict(OBPainter *painter) : d(new OBDepictPrivate)
117
0
  {
118
0
    d->painter = painter;
119
0
  }
120
121
  OBDepict::OBDepict(OBPainter *painter, bool withBall, bool symbolOnBall)
122
0
  {
123
0
    if(withBall)
124
0
      d = new OBDepictPrivateBallAndStick(symbolOnBall);
125
0
    else
126
0
      d = new OBDepictPrivate();
127
0
    d->painter = painter;
128
0
  }
129
130
  OBDepict::~OBDepict()
131
0
  {
132
0
    delete d->mol;
133
0
    d->mol = nullptr;
134
0
    delete d;
135
0
  }
136
137
  void OBDepict::SetBondLength(double length)
138
0
  {
139
0
    d->bondLength = length;
140
0
  }
141
142
  double OBDepict::GetBondLength() const
143
0
  {
144
0
    return d->bondLength;
145
0
  }
146
147
  void OBDepict::SetPenWidth(double width)
148
0
  {
149
0
    d->penWidth = width;
150
0
    d->painter->SetPenWidth(width);
151
0
  }
152
153
  double OBDepict::GetPenWidth() const
154
0
  {
155
0
    return d->penWidth;
156
0
  }
157
158
  void OBDepict::SetBondSpacing(double spacing)
159
0
  {
160
0
    d->bondSpacing = spacing;
161
0
  }
162
163
  double OBDepict::GetBondSpacing() const
164
0
  {
165
0
    return d->bondSpacing;
166
0
  }
167
168
  void OBDepict::SetBondWidth(double width)
169
0
  {
170
0
    d->bondWidth = width;
171
0
  }
172
173
  double OBDepict::GetBondWidth() const
174
0
  {
175
0
    return d->bondWidth;
176
0
  }
177
178
/*  void OBDepict::SetDrawingTerminalCarbon(bool enabled)
179
  {
180
    d->drawTerminalC = enabled;
181
  }
182
183
  bool OBDepict::GetDrawingTerminalCarbon() const
184
  {
185
    return d->drawTerminalC;
186
  }
187
*/
188
  void OBDepict::SetOption(unsigned opts)
189
0
  {
190
0
    d->options |= opts;
191
0
  }
192
193
  unsigned OBDepict::GetOptions() const
194
0
  {
195
0
    return d->options;
196
0
  }
197
  void OBDepict::ClearOptions()
198
0
  {
199
0
    d->options = 0;
200
0
  }
201
202
  void OBDepict::SetFontFamily(const std::string &family)
203
0
  {
204
0
    d->fontFamily = family;
205
0
    d->painter->SetFontFamily(family);
206
0
  }
207
208
  const std::string& OBDepict::GetFontFamily() const
209
0
  {
210
0
    return d->fontFamily;
211
0
  }
212
213
  void OBDepict::SetFontSize(int pointSize, bool subscript)
214
0
  {
215
0
    if (subscript) {
216
0
      d->subscriptSize = pointSize;
217
0
      return;
218
0
    }
219
220
0
    d->fontSize = pointSize;
221
0
    d->subscriptSize = (int)(0.85 * pointSize);
222
0
  }
223
224
  int OBDepict::GetFontSize(bool subscript) const
225
0
  {
226
0
    if (subscript)
227
0
      return d->subscriptSize;
228
0
    return d->fontSize;
229
0
  }
230
231
  void OBDepict::SetAliasMode(bool b)
232
0
  {
233
0
    d->aliasMode = b;
234
0
  }
235
236
  //Color is not quite properly integrated into OBDepict, but is needed if
237
  //element-dependent coloring is to be used.
238
  void OBDepict::SetBondColor(const std::string& scolor)
239
0
  {
240
0
    d->bondColor = scolor;
241
0
  }
242
243
  int GetLabelAlignment(OBAtom *atom)
244
0
  {
245
    // compute the sum of the bond vectors, this gives
246
0
    vector3 direction(VZero);
247
0
    OBBondIterator i;
248
0
    for (OBAtom *nbr = atom->BeginNbrAtom(i); nbr; nbr = atom->NextNbrAtom(i))
249
0
      direction += atom->GetVector() - nbr->GetVector();
250
251
0
    const double bias = -0.1; //towards left-alignment, which is more natural
252
0
    int alignment = 0;
253
0
    if ((atom->GetExplicitDegree() == 2) && (abs(direction.y()) > abs(direction.x()))) {
254
0
      if (direction.y() <= 0.0)
255
0
        alignment = Up;
256
0
      else
257
0
        alignment = Down;
258
0
    } else {
259
0
      if (direction.x() < bias)
260
0
        alignment = Right;
261
0
      else
262
0
        alignment = Left;
263
0
    }
264
265
0
    return alignment;
266
0
  }
267
268
  unsigned int GetAtomSymClass(OBAtom *atom)
269
0
  {
270
0
    OBPairData *pd = dynamic_cast<OBPairData*>(atom->GetParent()->GetData("OpenBabel Symmetry Classes"));
271
0
    if (pd) {
272
273
0
      cout << "same? = " << pd->GetValue() << endl;
274
275
0
      istringstream iss(pd->GetValue());
276
0
      std::vector<unsigned int> symmetry_classes;
277
0
      copy(istream_iterator<unsigned int>(iss),
278
0
           istream_iterator<unsigned int>(),
279
0
           back_inserter<vector<unsigned int> >(symmetry_classes));
280
      // Now find the number of unique elements
281
0
      vector<unsigned int> copy_sym = symmetry_classes;
282
0
      sort(copy_sym.begin(), copy_sym.end());
283
0
      vector<unsigned int>::iterator end_pos = unique(copy_sym.begin(), copy_sym.end()); // Requires sorted elements
284
0
      int nclasses = end_pos - copy_sym.begin();
285
286
0
      cout << "sym_class[" << atom->GetIndex() << "] = " << symmetry_classes.at(atom->GetIndex()) << endl;
287
0
      return symmetry_classes.at(atom->GetIndex());
288
0
    }
289
290
0
    return 99;
291
0
  }
292
293
  bool OBDepict::AddAtomLabels(AtomLabelType type)
294
0
  {
295
0
    d->painter->SetPenColor(OBColor("red"));
296
0
    d->painter->SetFillColor(OBColor("red"));
297
0
    d->painter->SetFontSize((int)(GetFontSize() * 0.8));// smaller text
298
0
    OBAtomIterator i;
299
0
    for (OBAtom *atom = d->mol->BeginAtom(i); atom; atom = d->mol->NextAtom(i)) {
300
0
      vector3 pos(atom->GetVector());
301
0
      std::stringstream ss;
302
0
      switch (type) {
303
0
        case AtomId:
304
0
          ss << atom->GetId();
305
0
          d->painter->DrawText(pos.x(), pos.y(), ss.str());
306
0
          break;
307
0
        case AtomSymmetryClass:
308
0
          ss << GetAtomSymClass(atom);
309
0
          d->painter->DrawText(pos.x(), pos.y(), ss.str());
310
0
          break;
311
0
        case AtomIndex:
312
0
          ss << atom->GetIdx();
313
0
          d->painter->DrawText(pos.x(), pos.y(), ss.str());
314
0
          break;
315
316
0
        default:
317
0
          break;
318
0
      }
319
0
    }
320
321
0
    return true;
322
0
  }
323
324
  void OBDepictPrivate::DrawRing(OBRing *ring, OBBitVec &drawnBonds)
325
0
  {
326
0
    std::vector<int> indexes = ring->_path;
327
0
    vector3 center(VZero);
328
0
    for (std::vector<int>::iterator l = indexes.begin(); l != indexes.end(); ++l) {
329
0
      center += mol->GetAtom(*l)->GetVector();
330
0
    }
331
0
    center /= indexes.size();
332
333
0
    for (unsigned int l = 0; l < indexes.size(); ++l) {
334
0
      OBAtom *begin = mol->GetAtom(indexes[l]);
335
0
      OBAtom *end;
336
0
      if (l+1 < indexes.size())
337
0
        end = mol->GetAtom(indexes[l+1]);
338
0
      else
339
0
        end = mol->GetAtom(indexes[0]);
340
341
0
      OBBond *ringBond = mol->GetBond(begin, end);
342
0
      if (drawnBonds.BitIsSet(ringBond->GetId()))
343
0
        continue;
344
345
0
      if((options & OBDepict::internalColor) && ringBond->HasData("color"))
346
0
        painter->SetPenColor(OBColor(ringBond->GetData("color")->GetValue()));
347
0
      else
348
0
        painter->SetPenColor(bondColor);
349
350
0
      DrawRingBond(begin, end, center, ringBond->GetBondOrder());
351
0
      drawnBonds.SetBitOn(ringBond->GetId());
352
0
    }
353
0
  }
354
355
  void OBDepictPrivate::DrawAromaticRing(OBRing *ring, OBBitVec &drawnBonds)
356
0
  {
357
0
    DrawRing(ring, drawnBonds);
358
0
  }
359
360
  enum Radical { NOT_RADICAL, ONE_DOT, TWO_DOT };
361
362
  // Assign 0, 1, or 2 radical dots
363
  // - if spin is specified, then this determines the number of dots
364
  // - otherwise, the degree of undervalence determines it
365
  static Radical AssignRadicalDots(OBAtom* atom)
366
0
  {
367
0
    unsigned int spin = atom->GetSpinMultiplicity();
368
0
    if (spin)
369
0
      return spin == 2 ? TWO_DOT : ONE_DOT;
370
371
0
    unsigned int actualvalence = atom->GetTotalValence();
372
0
    unsigned int typicalvalence = GetTypicalValence(atom->GetAtomicNum(), actualvalence, atom->GetFormalCharge());
373
0
    int diff = typicalvalence - actualvalence;
374
0
    if (diff <= 0)
375
0
      return NOT_RADICAL;
376
0
    return diff == 2 ? TWO_DOT : ONE_DOT;
377
0
  }
378
379
  bool OBDepict::DrawMolecule(OBMol *mol)
380
0
  {
381
0
    if (!d->painter)
382
0
      return false;
383
384
0
    delete d->mol;
385
0
    d->mol = new OBMol(*mol); // Copy it
386
387
0
    double width=0.0, height=0.0;
388
389
0
    OBAtom *atom;
390
0
    OBBondIterator j;
391
0
    OBAtomIterator i;
392
393
394
    // Determine which should be wedge and hash bonds...
395
    // Note: we need to do this before we invert the y-coordinate for depiction
396
0
    std::map<OBBond*, enum OBStereo::BondDirection> updown;
397
0
    std::map<OBBond*, OBStereo::Ref> from;
398
0
    TetStereoToWedgeHash(*d->mol, updown, from);
399
400
0
    if(mol->NumAtoms()>0) {
401
      // scale bond lengths and invert the y coordinate (both SVG and Cairo use top left as the origin)
402
0
      double bondLengthSum = 0.0;
403
0
      for (OBBond *bond = mol->BeginBond(j); bond; bond = mol->NextBond(j))
404
0
        bondLengthSum += bond->GetLength();
405
0
      const double averageBondLength = bondLengthSum / mol->NumBonds();
406
0
      double f;
407
0
      if(mol->NumBonds()>0) {
408
0
        f = d->bondLength / averageBondLength;
409
0
      } else if(mol->NumAtoms()>1){
410
        /* In molecules without bonds but more than one atom,
411
           use the minimum atom distance as a substitute for average bond length. */
412
0
        f = DBL_MAX;
413
0
        OBAtomIterator i2;
414
0
        OBAtom *atom2;
415
0
        vector3 a1pos;
416
0
        double currdist;
417
0
        for (atom = d->mol->BeginAtom(i); atom; atom = d->mol->NextAtom(i)) {
418
0
          a1pos = atom->GetVector();
419
0
          for (atom2 = d->mol->BeginAtom(i2); atom2; atom2 = d->mol->NextAtom(i2)) {
420
0
            if(atom != atom2) {
421
0
              currdist = a1pos.distSq(atom2->GetVector());
422
0
              if(currdist < f && currdist != 0)
423
0
                f = currdist;
424
0
            }
425
0
          }
426
0
        }
427
0
        f = d->bondLength / sqrt(f);
428
0
      } else
429
0
        f = 1.0;
430
0
      for (atom = d->mol->BeginAtom(i); atom; atom = d->mol->NextAtom(i))
431
0
        atom->SetVector(atom->GetX() * f, - atom->GetY() * f, atom->GetZ());
432
433
      // find min/max values
434
0
      double min_x, max_x;
435
0
      double min_y, max_y;
436
0
      double min_z, max_z;
437
0
      atom = d->mol->BeginAtom(i);
438
0
      if (atom != nullptr) {
439
0
        min_x = max_x = atom->GetX();
440
0
        min_y = max_y = atom->GetY();
441
0
        min_z = max_z = atom->GetZ();
442
0
        for (atom = d->mol->NextAtom(i); atom; atom = d->mol->NextAtom(i)) {
443
0
          min_x = std::min(min_x, atom->GetX());
444
0
          max_x = std::max(max_x, atom->GetX());
445
0
          min_y = std::min(min_y, atom->GetY());
446
0
          max_y = std::max(max_y, atom->GetY());
447
0
          min_z = std::min(min_z, atom->GetZ());
448
0
          max_z = std::max(max_z, atom->GetZ());
449
0
        }
450
0
      }
451
452
0
      double margin;
453
0
      if (d->options & noMargin)
454
0
        margin = 5.0;
455
0
      else
456
0
        margin = 40.0;
457
      // translate all atoms so the bottom-left atom is at margin,margin
458
0
      for (atom = d->mol->BeginAtom(i); atom; atom = d->mol->NextAtom(i))
459
0
        atom->SetVector(atom->GetX() - min_x + margin, atom->GetY() - min_y + margin, atom->GetZ());
460
461
0
      width  = max_x - min_x + 2*margin;
462
0
      height = max_y - min_y + 2*margin;
463
464
0
      d->zScale = max_z - min_z;
465
0
      if (fabs(d->zScale) < 1.0e-1)
466
0
        d->zScale = 0.0;
467
0
      d->zMin = min_z;
468
469
      //d->painter->SetPenWidth(d->penWidth);
470
      //d->painter->SetPenColor(d->pen));
471
      //d->painter->SetFillColor(OBColor("black"));
472
0
    }
473
474
0
    d->painter->NewCanvas(width, height);
475
476
    // Identify and remember the ring bonds according to the SSSR
477
    // - note that OBBond->IsInRing() includes bonds not included in the SSSR as the SSSR excludes very large rings
478
0
    std::vector<OBRing*> rings(mol->GetSSSR());
479
0
    OBBitVec ringBonds;
480
0
    for (std::vector<OBRing*>::iterator k = rings.begin(); k != rings.end(); ++k) {
481
0
      OBRing *ring = *k;
482
0
      std::vector<int> indexes = ring->_path;
483
0
      for (unsigned int l = 0; l < indexes.size(); ++l) {
484
0
        OBAtom *begin = d->mol->GetAtom(indexes[l]);
485
0
        OBAtom *end;
486
0
        if (l+1 < indexes.size())
487
0
          end = d->mol->GetAtom(indexes[l+1]);
488
0
        else
489
0
          end = d->mol->GetAtom(indexes[0]);
490
491
0
        OBBond *ringBond = d->mol->GetBond(begin, end);
492
0
        ringBonds.SetBitOn(ringBond->GetId());
493
0
      }
494
0
    }
495
496
    // draw bonds
497
0
    for (OBBond *bond = d->mol->BeginBond(j); bond; bond = d->mol->NextBond(j)) {
498
0
      OBAtom *begin = bond->GetBeginAtom();
499
0
      OBAtom *end = bond->GetEndAtom();
500
0
      if((d->options & internalColor) && bond->HasData("color"))
501
0
        d->painter->SetPenColor(OBColor(bond->GetData("color")->GetValue()));
502
0
      else
503
0
        d->painter->SetPenColor(d->bondColor);
504
505
0
      if(from.find(bond)!=from.end()) {
506
        //is a wedge or hash bond
507
0
        if(from[bond]==bond->GetEndAtom()->GetId())
508
0
          swap(begin, end);
509
0
        if(updown[bond]==OBStereo::UpBond)
510
0
          d->DrawWedge(begin, end);
511
0
        else if(updown[bond]==OBStereo::DownBond)
512
0
          d->DrawHash(begin, end);
513
0
        else {
514
          //This is a bond to a chiral center specified as unknown
515
0
          d->DrawWobblyBond(begin, end);
516
0
        }
517
0
      }
518
0
      else if (!ringBonds.BitIsSet(bond->GetId())) { // Ring bonds are handled below
519
0
        bool crossed_dbl_bond = false;
520
0
        OBStereoFacade sf(d->mol);
521
0
        if (sf.HasCisTransStereo(bond->GetId())) {
522
0
          OBCisTransStereo *ct = sf.GetCisTransStereo(bond->GetId());
523
0
          if (!ct->GetConfig().specified)
524
0
            crossed_dbl_bond = true;
525
0
        }
526
0
        d->DrawSimpleBond(begin, end, bond->GetBondOrder(), crossed_dbl_bond);
527
0
      }
528
0
    }
529
530
    // draw ring bonds
531
0
    OBBitVec drawnBonds;
532
    // draw aromatic rings first, looks better since all double bonds will
533
    // be inside aromatic rings
534
0
    for (std::vector<OBRing*>::iterator k = rings.begin(); k != rings.end(); ++k) {
535
0
      OBRing *ring = *k;
536
0
      if (ring->IsAromatic())
537
0
        d->DrawAromaticRing(ring, drawnBonds);
538
0
    }
539
    // draw aliphatic rings
540
0
    for (std::vector<OBRing*>::iterator k = rings.begin(); k != rings.end(); ++k) {
541
0
      OBRing *ring = *k;
542
0
      if (!ring->IsAromatic())
543
0
        d->DrawRing(ring, drawnBonds);
544
0
    }
545
546
0
    vector<pair<OBAtom*,double> > zsortedAtoms;
547
0
    vector<int> zsorted;
548
0
    unsigned int a;
549
0
    for (a = 0, atom = d->mol->BeginAtom(i) ; atom ; atom = d->mol->NextAtom(i), ++a)
550
0
      {
551
0
        pair<OBAtom*,double> entry(atom, atom->GetVector().z());
552
0
        zsortedAtoms.push_back(entry);
553
0
      }
554
0
    sort(zsortedAtoms.begin(), zsortedAtoms.end(), SortAtomZ);
555
0
    unsigned int max = zsortedAtoms.size();
556
0
    for (a = 0 ; a < max ; a++ ) {
557
0
      atom   = zsortedAtoms[a].first;
558
0
      double x = atom->GetX();
559
0
      double y = atom->GetY();
560
561
0
      d->DrawAtom(atom);
562
563
      // draw atom labels
564
0
      int alignment = GetLabelAlignment(atom);
565
0
      bool rightAligned = false;
566
0
      switch (alignment) {
567
0
        case Right:
568
0
          rightAligned = true;
569
          /* no break */
570
0
        default:
571
0
          break;
572
0
      }
573
574
0
      if((d->options & internalColor) && atom->HasData("color"))
575
0
        d->painter->SetPenColor(OBColor(atom->GetData("color")->GetValue()));
576
0
      else if(d->options & bwAtoms)
577
0
        d->painter->SetPenColor(d->bondColor);
578
0
      else {
579
0
        double r, g, b;
580
0
        OBElements::GetRGB(atom->GetAtomicNum(), &r, &g, &b);
581
0
        d->painter->SetPenColor(OBColor(r, g, b));
582
0
      }
583
584
      //charge and radical
585
0
      int charge = atom->GetFormalCharge();
586
0
      Radical radical = AssignRadicalDots(atom); // none, one or two
587
0
      if(charge || radical != NOT_RADICAL) {
588
0
        OBFontMetrics metrics = d->painter->GetFontMetrics("N");
589
0
        double yoffset = d->HasLabel(atom) ? -0.2 * metrics.height : -0.2 * metrics.height;
590
        /*switch (GetLabelAlignment(atom)) {
591
          case Up:
592
          case Left:
593
          case Right:
594
            yoffset = - 1.2 * metrics.height;
595
        }*/
596
0
        stringstream ss;
597
0
        if(charge) {
598
0
          if(abs(charge)!=1)
599
0
            ss << abs(charge);
600
0
          if(charge>0)
601
0
            ss << '+';
602
0
          else if (charge<-1) //use underscore for single negative charge and minus if multiple
603
0
            ss << '-';
604
0
          else
605
0
          {
606
0
            ss << '_';
607
0
            yoffset -= 0.5 * metrics.height;
608
0
          }
609
0
        }
610
0
        d->painter->DrawText(x + 0.4*metrics.width, y+yoffset, ss.str());
611
0
        if (radical != NOT_RADICAL) {
612
0
          string radchars;
613
0
          radchars = radical == ONE_DOT ? "." : "..";
614
0
          d->painter->SetFontSize(2 * metrics.fontSize);
615
0
          d->painter->DrawText(x + (0.4 + ss.str().size())*metrics.width,
616
0
            y + yoffset, radchars);
617
0
        }
618
0
        d->painter->SetFontSize(metrics.fontSize);//restore
619
0
      }
620
621
0
      if (atom->GetAtomicNum() == OBElements::Carbon) {
622
0
        if(!(d->options & drawAllC))
623
0
        {
624
0
          if (atom->GetExplicitDegree() > 1)
625
0
            continue;
626
0
          if ((atom->GetExplicitDegree() == 1) && !(d->options & drawTermC))//!d->drawTerminalC)
627
0
            continue;
628
0
        }
629
0
      }
630
631
0
      bool written = false;
632
0
      stringstream ss;
633
634
      //For unexpanded aliases use appropriate form of alias instead of element symbol, Hs, etc
635
0
      AliasData* ad = nullptr;
636
0
      if (d->aliasMode && atom->HasData(AliasDataType))
637
0
        ad = static_cast<AliasData*>(atom->GetData(AliasDataType));
638
0
      if(ad && !ad->IsExpanded())
639
0
      {
640
0
        ss <<ad->GetAlias(rightAligned);
641
0
        OBColor aliasColor = !ad->GetColor().empty() ? ad->GetColor() : d->bondColor;
642
0
          d->painter->SetPenColor(aliasColor);
643
0
        written = true;
644
0
      }
645
646
0
      if (!written) {
647
        //Atoms with no AliasData, but 0 atomic num and atomclass==n are output as Rn
648
0
        if (atom->GetAtomicNum()==0) {
649
0
          OBGenericData *data = atom->GetData("Atom Class");
650
0
          if (data) {
651
0
            OBPairInteger* acdata = dynamic_cast<OBPairInteger*>(data); // Could replace with C-style cast if willing to live dangerously
652
0
            if (acdata) {
653
0
              int ac = acdata->GetGenericValue();
654
0
              if (ac >= 0) {
655
0
                ss << 'R' << ac;
656
0
                d->painter->SetPenColor(OBColor("black"));
657
0
                written = true;
658
0
              }
659
0
            }
660
0
          }
661
0
        }
662
0
      }
663
664
0
      if (!written) {
665
0
        const char* atomSymbol;
666
0
        if(atom->GetAtomicNum() == OBElements::Hydrogen && atom->GetIsotope()>1)
667
0
          atomSymbol = atom->GetIsotope()==2 ? "D" : "T";
668
0
        else
669
0
          atomSymbol = OBElements::GetSymbol(atom->GetAtomicNum());
670
671
0
        unsigned int hCount = atom->GetImplicitHCount();
672
        // LPW: The allExplicit option will omit the drawing of extra hydrogens
673
        // to fill the valence.
674
0
        if((d->options & allExplicit))
675
0
            hCount = 0;
676
        // rightAligned:
677
        //   false  CH3
678
        //   true   H3C
679
0
        if (hCount && rightAligned)
680
0
          ss << "H";
681
0
        if ((hCount > 1) && rightAligned)
682
0
          ss << hCount;
683
0
        ss << atomSymbol;
684
0
        if (hCount && !rightAligned)
685
0
          ss << "H";
686
0
        if ((hCount > 1) && !rightAligned)
687
0
          ss << hCount;
688
0
      }
689
0
      d->DrawAtomLabel(ss.str(), alignment, vector3(x, y, 0.0));
690
0
    }
691
692
0
    return true;
693
0
  }
694
695
  void OBDepictPrivate::DrawWobblyBond(OBAtom *beginAtom, OBAtom *endAtom)
696
0
  {
697
0
    vector3 begin = beginAtom->GetVector();
698
0
    vector3 end = endAtom->GetVector();
699
0
    vector3 vb = end - begin;
700
701
0
    if (HasLabel(beginAtom))
702
0
      begin += 0.33 * vb;
703
0
    if (HasLabel(endAtom))
704
0
      end -= 0.33 * vb;
705
706
0
    vb = end - begin; // Resize the extents of the vb vector
707
708
0
    vector3 orthogonalLine = cross(vb, VZ);
709
0
    orthogonalLine.normalize();
710
0
    orthogonalLine *= 0.5 * bondWidth;
711
712
0
    double lines[6] = { 0.20, 0.36, 0.52, 0.68, 0.84, 1.0 };
713
714
    // This code is adapted from DrawWedge():
715
    // What we do is just join up the opposite ends of each of the wedge strokes
716
    // to create a zig-zag bond
717
718
0
    double oldx, oldy, newx, newy;
719
0
    oldx = begin.x();
720
0
    oldy = begin.y();
721
0
    int sign = 1;
722
0
    for (int k = 0; k < 6; ++k) {
723
0
      double w = lines[k];
724
0
      newx = begin.x() + vb.x() * w + sign * orthogonalLine.x() * w;
725
0
      newy = begin.y() + vb.y() * w + sign * orthogonalLine.y() * w;
726
0
      painter->DrawLine(oldx, oldy, newx, newy);
727
0
      oldx = newx;
728
0
      oldy = newy;
729
0
      sign = -sign;
730
0
    }
731
0
  }
732
733
  void OBDepictPrivate::DrawWedge(OBAtom *beginAtom, OBAtom *endAtom)
734
0
  {
735
0
    vector3 begin = beginAtom->GetVector();
736
0
    vector3 end = endAtom->GetVector();
737
0
    vector3 vb = end - begin;
738
739
0
    if (HasLabel(beginAtom))
740
0
      begin += 0.33 * vb;
741
0
    if (HasLabel(endAtom))
742
0
      end -= 0.33 * vb;
743
744
0
    vector3 orthogonalLine = cross(vb, VZ);
745
0
    orthogonalLine.normalize();
746
0
    orthogonalLine *= 0.5 * bondWidth;
747
0
    std::vector<std::pair<double,double> > points;
748
749
0
    points.push_back(std::pair<double,double>(begin.x(), begin.y()));
750
0
    points.push_back(std::pair<double,double>(end.x() + orthogonalLine.x(),
751
0
                                              end.y() + orthogonalLine.y()));
752
0
    points.push_back(std::pair<double,double>(end.x() - orthogonalLine.x(),
753
0
                                              end.y() - orthogonalLine.y()));
754
0
    painter->DrawPolygon(points);
755
0
  }
756
757
  void OBDepictPrivate::DrawHash(OBAtom *beginAtom, OBAtom *endAtom)
758
0
  {
759
0
    vector3 begin = beginAtom->GetVector();
760
0
    vector3 end = endAtom->GetVector();
761
0
    vector3 vb = end - begin;
762
763
0
    if (HasLabel(beginAtom))
764
0
      begin += 0.33 * vb;
765
0
    if (HasLabel(endAtom))
766
0
      end -= 0.33 * vb;
767
768
0
    vb = end - begin; // Resize the extents of the vb vector
769
770
0
    vector3 orthogonalLine = cross(vb, VZ);
771
0
    orthogonalLine.normalize();
772
0
    orthogonalLine *= 0.5 * bondWidth;
773
774
0
    double lines[6] = { 0.20, 0.36, 0.52, 0.68, 0.84, 1.0 };
775
0
    double oldwidth = painter->GetPenWidth();
776
0
    painter->SetPenWidth(1);
777
0
    for (int k = 0; k < 6; ++k) {
778
0
      double w = lines[k];
779
0
      painter->DrawLine(begin.x() + vb.x() * w + orthogonalLine.x() * w,
780
0
                        begin.y() + vb.y() * w + orthogonalLine.y() * w,
781
0
                        begin.x() + vb.x() * w - orthogonalLine.x() * w,
782
0
                        begin.y() + vb.y() * w - orthogonalLine.y() * w);
783
0
    }
784
0
    painter->SetPenWidth(oldwidth);
785
0
  }
786
787
  void OBDepictPrivate::DrawSimpleBond(OBAtom *beginAtom, OBAtom *endAtom, int order, bool crossed_dbl_bond)
788
0
  {
789
0
    vector3 begin = beginAtom->GetVector();
790
0
    vector3 end = endAtom->GetVector();
791
0
    vector3 vb = end - begin;
792
793
0
    vb.normalize();
794
795
0
    if (HasLabel(beginAtom))
796
0
      begin += 13. * vb; // Length is normally 40
797
0
    if (HasLabel(endAtom))
798
0
      end -= 13. * vb;
799
800
0
    if (order == 1) {
801
0
      painter->DrawLine(begin.x(), begin.y(), end.x(), end.y());
802
0
    } else if (order == 2) {
803
0
      vector3 orthogonalLine = cross(end - begin, VZ).normalize();
804
805
0
      bool useAsymmetricDouble = options & OBDepict::asymmetricDoubleBond;
806
0
      if (HasLabel(beginAtom) && HasLabel(endAtom))
807
0
        useAsymmetricDouble = false;
808
0
      if (HasLabel(beginAtom) && endAtom->GetExplicitDegree() == 3)
809
0
        useAsymmetricDouble = false;
810
0
      if (HasLabel(endAtom) && beginAtom->GetExplicitDegree() == 3)
811
0
        useAsymmetricDouble = false;
812
0
      if (crossed_dbl_bond)
813
0
        useAsymmetricDouble = false; // The bond looks very strange otherwise in the case of cis
814
815
0
      if (!useAsymmetricDouble) {
816
        // style1
817
        //
818
        // -----------
819
        // -----------
820
0
        vector3 offset = orthogonalLine * 0.5 * bondSpacing;
821
0
        if (!crossed_dbl_bond) {
822
0
          painter->DrawLine(begin.x() + offset.x(), begin.y() + offset.y(),
823
0
                            end.x() + offset.x(), end.y() + offset.y());
824
0
          painter->DrawLine(begin.x() - offset.x(), begin.y() - offset.y(),
825
0
                            end.x() - offset.x(), end.y() - offset.y());
826
0
        }
827
0
        else {
828
0
          painter->DrawLine(begin.x() + offset.x(), begin.y() + offset.y(),
829
0
                            end.x() - offset.x(), end.y() - offset.y());
830
0
          painter->DrawLine(begin.x() - offset.x(), begin.y() - offset.y(),
831
0
                            end.x() + offset.x(), end.y() + offset.y());
832
0
        }
833
0
      } else {
834
        // style2
835
        //
836
        //   -------
837
        // -----------
838
0
        vector3 offset1 = orthogonalLine * /*0.5 * */ bondSpacing;
839
0
        vector3 offset2 = vb * /*0.5 * */ bondSpacing;
840
0
        vector3 offset3 = - vb * /*0.5 * */ bondSpacing;
841
842
0
        if (HasLabel(beginAtom))
843
0
          offset2 = VZero;
844
0
        if (HasLabel(endAtom))
845
0
          offset3 = VZero;
846
847
0
        painter->DrawLine(begin.x(), begin.y(), end.x(), end.y());
848
0
        painter->DrawLine(begin.x() + offset1.x() + offset2.x(),
849
0
                          begin.y() + offset1.y() + offset2.y(),
850
0
                          end.x() + offset1.x() + offset3.x(),
851
0
                          end.y() + offset1.y() + offset3.y());
852
0
      }
853
0
    } else if (order == 3) {
854
0
      vector3 orthogonalLine = cross(end - begin, VZ).normalize();
855
0
      vector3 offset = orthogonalLine * 0.7 * bondSpacing;
856
0
      painter->DrawLine(begin.x(), begin.y(), end.x(), end.y());
857
0
      painter->DrawLine(begin.x() + offset.x(), begin.y() + offset.y(),
858
0
                        end.x() + offset.x(), end.y() + offset.y());
859
0
      painter->DrawLine(begin.x() - offset.x(), begin.y() - offset.y(),
860
0
                        end.x() - offset.x(), end.y() - offset.y());
861
0
    }
862
0
  }
863
864
  void OBDepictPrivate::DrawRingBond(OBAtom *beginAtom, OBAtom *endAtom, const vector3 &center, int order)
865
0
  {
866
0
    if (order != 2) {
867
0
      DrawSimpleBond(beginAtom, endAtom, order);
868
0
      return;
869
0
    }
870
871
0
    vector3 begin = beginAtom->GetVector();
872
0
    vector3 end = endAtom->GetVector();
873
874
0
    vector3 vb = (end - begin).normalize();
875
0
    vector3 orthogonalLine = cross(vb, VZ)/*.normalize()*/;
876
0
    vector3 spacing = orthogonalLine * bondSpacing * 1.2;
877
0
    vector3 offset = vb * bondSpacing;
878
0
    if ((begin + spacing - center).length() > (begin - spacing - center).length())
879
0
      spacing *= -1.0;
880
881
0
    vector3 vbb = end - begin;
882
0
    if (HasLabel(beginAtom))
883
0
      begin += 0.33 * vbb;
884
0
    if (HasLabel(endAtom))
885
0
      end -= 0.33 * vbb;
886
0
    painter->DrawLine(begin.x(), begin.y(), end.x(), end.y());
887
888
0
    if (HasLabel(beginAtom))
889
0
      begin -= 0.10 * vbb;
890
0
    if (HasLabel(endAtom))
891
0
      end += 0.10 * vbb;
892
0
    painter->DrawLine(begin.x() + spacing.x() + offset.x(), begin.y() + spacing.y() + offset.y(),
893
0
                      end.x() + spacing.x() - offset.x(), end.y() + spacing.y() - offset.y());
894
0
  }
895
896
  void OBDepictPrivate::DrawAtom(OBAtom *atom)
897
0
  {
898
0
  }
899
900
  void OBDepictPrivate::DrawAtomLabel(const std::string &label, int alignment, const vector3 &pos)
901
0
  {
902
   /*
903
    cout << "FontMetrics(" << label << "):" << endl;
904
    cout << "  ascent = " << metrics.ascent << endl;
905
    cout << "  descent = " << metrics.descent << endl;
906
    cout << "  width = " << metrics.width << endl;
907
    cout << "  height = " << metrics.height << endl;
908
909
    painter->SetFillColor(OBColor("white"));
910
    painter->SetPenColor(OBColor("white"));
911
    painter->DrawCircle(pos.x(), pos.y(), metrics.ascent / 2);
912
    painter->SetPenColor(OBColor("black"));
913
    */
914
915
    // compute the total width
916
0
    double totalWidth = 0.0;
917
0
    if ((alignment == Right) || (alignment == Left) || (label.find("H") == std::string::npos)) {
918
0
      for (unsigned int i = 0; i < label.size(); ++i) {
919
0
        if (!isalpha(label[i])) {
920
0
          painter->SetFontSize(subscriptSize);
921
0
          totalWidth += painter->GetFontMetrics(label.substr(i, 1)).width;
922
0
        } else {
923
0
          painter->SetFontSize(fontSize);
924
0
          totalWidth += painter->GetFontMetrics(label.substr(i, 1)).width;
925
0
        }
926
0
      }
927
0
    } else {
928
0
      painter->SetFontSize(fontSize);
929
0
      totalWidth = painter->GetFontMetrics(label.substr(0, label.find("H"))).width;
930
0
      double width = 0.0;
931
0
      for (unsigned int i = label.find("H"); i < label.size(); ++i) {
932
0
        if (!isalpha(label[i])) {
933
0
          painter->SetFontSize(subscriptSize);
934
0
          width += painter->GetFontMetrics(label.substr(i, 1)).width;
935
0
        } else {
936
0
          painter->SetFontSize(fontSize);
937
0
          width += painter->GetFontMetrics(label.substr(i, 1)).width;
938
0
        }
939
0
      }
940
941
0
      if (width > totalWidth)
942
0
        totalWidth = width;
943
0
    }
944
945
0
    painter->SetFontSize(fontSize);
946
0
    OBFontMetrics metrics = painter->GetFontMetrics(label);
947
948
949
0
    std::string str, subscript;
950
    // compute the horizontal starting position
951
0
    double xOffset, yOffset, yOffsetSubscript;
952
0
    switch (alignment) {
953
0
      case Right:
954
0
        xOffset = 0.5 * painter->GetFontMetrics(label.substr(0, 1)).width -
955
0
                  painter->GetFontMetrics(label).width;
956
0
        break;
957
0
      case Left:
958
0
        xOffset = - 0.5 * painter->GetFontMetrics(label.substr(label.size()-1, 1)).width;
959
0
        break;
960
0
      case Up:
961
0
      case Down:
962
0
        if (label.find("H") != std::string::npos)
963
0
          xOffset = - 0.5 * painter->GetFontMetrics(label.substr(0, label.find("H"))).width;
964
0
        else
965
0
          xOffset = - 0.5 * totalWidth;
966
0
        break;
967
0
      default:
968
0
        xOffset = - 0.5 * totalWidth;
969
0
        break;
970
0
    }
971
972
    // compute the vertical starting position
973
0
    yOffset = 0.5 * (metrics.ascent /*- metrics.descent*/);
974
0
    yOffsetSubscript = yOffset - metrics.descent;
975
0
    double xInitial = xOffset;
976
977
0
    for (unsigned int i = 0; i < label.size(); ++i) {
978
0
      if (label[i] == 'H') {
979
0
        if ((alignment == Up) || (alignment == Down))
980
0
          if (!str.empty()) {
981
            // write the current string
982
0
            painter->SetFontSize(fontSize);
983
0
            painter->DrawText(pos.x() + xOffset, pos.y() + yOffset, str);
984
0
            if (alignment == Down) {
985
0
              yOffset += metrics.fontSize;
986
0
              yOffsetSubscript += metrics.fontSize;
987
0
            } else {
988
0
              yOffset -= metrics.fontSize;
989
0
              yOffsetSubscript -= metrics.fontSize;
990
0
            }
991
0
            xOffset = xInitial;
992
0
            str.clear();
993
0
          }
994
0
      }
995
996
997
0
      if (!isalpha(label[i])) {
998
0
        if (!str.empty()) {
999
          // write the current string
1000
0
          painter->SetFontSize(fontSize);
1001
0
          OBFontMetrics metrics = painter->GetFontMetrics(str);
1002
0
          painter->DrawText(pos.x() + xOffset, pos.y() + yOffset, str);
1003
0
          xOffset += metrics.width;
1004
0
          str.clear();
1005
0
        }
1006
1007
0
        subscript += label.substr(i, 1);
1008
0
      } else {
1009
0
        if (!subscript.empty()) {
1010
          // write the current subscript
1011
0
          painter->SetFontSize(subscriptSize);
1012
0
          OBFontMetrics metrics = painter->GetFontMetrics(subscript);
1013
0
          painter->DrawText(pos.x() + xOffset, pos.y() + yOffsetSubscript, subscript);
1014
0
          xOffset += metrics.width;
1015
0
          subscript.clear();
1016
0
        }
1017
1018
0
        str += label.substr(i, 1);
1019
0
      }
1020
0
    }
1021
0
    if (!str.empty()) {
1022
0
      painter->SetFontSize(fontSize);
1023
0
      OBFontMetrics metrics = painter->GetFontMetrics(str);
1024
0
      painter->DrawText(pos.x() + xOffset, pos.y() + yOffset, str);
1025
0
    }
1026
0
    if (!subscript.empty()) {
1027
0
      painter->SetFontSize(subscriptSize);
1028
0
      OBFontMetrics metrics = painter->GetFontMetrics(subscript);
1029
0
      double yOffset = ispunct(subscript[subscript.size()-1]) || ispunct(subscript[0])
1030
0
        || (subscript.size()>1 && ispunct(subscript[1]))
1031
0
        ? -yOffsetSubscript : yOffsetSubscript;
1032
0
      painter->DrawText(pos.x() + xOffset, pos.y() + yOffset, subscript);
1033
0
    }
1034
1035
0
  }
1036
1037
  bool OBDepictPrivate::HasLabel(OBAtom *atom)
1038
0
  {
1039
0
    if (atom->GetAtomicNum() != OBElements::Carbon)
1040
0
      return true;
1041
0
    if ((options & OBDepict::drawAllC) || ((options & OBDepict::drawTermC) && (atom->GetExplicitDegree() == 1)))
1042
0
      return true;
1043
0
    return false;
1044
0
  }
1045
1046
  void OBDepictPrivate::SetWedgeAndHash(OBMol* mol)
1047
0
  {
1048
    // Remove any existing wedge and hash bonds
1049
0
    FOR_BONDS_OF_MOL(b,mol)  {
1050
0
      b->SetWedge(false);
1051
0
      b->SetHash(false);
1052
0
    }
1053
1054
0
    std::map<OBBond*, enum OBStereo::BondDirection> updown;
1055
0
    std::map<OBBond*, OBStereo::Ref> from;
1056
0
    std::map<OBBond*, OBStereo::Ref>::const_iterator from_cit;
1057
0
    TetStereoToWedgeHash(*mol, updown, from);
1058
1059
0
    for(from_cit=from.begin();from_cit!=from.end();++from_cit) {
1060
0
      OBBond* pbond = from_cit->first;
1061
0
      if(updown[pbond]==OBStereo::UpBond)
1062
0
        pbond->SetHash();
1063
0
      else if(updown[pbond]==OBStereo::DownBond)
1064
0
        pbond->SetWedge();
1065
0
    }
1066
0
  }
1067
  void OBDepictPrivateBallAndStick::DrawSimpleBond(OBAtom* beginAtom,
1068
  OBAtom* endAtom, int order, bool crossed_bond)
1069
0
  {
1070
0
    const vector3 begin = beginAtom->GetVector();
1071
0
    const vector3 end = endAtom->GetVector();
1072
0
    const vector3 vb = (end - begin).normalize();
1073
1074
    // calculate the expected opacity and width
1075
    // to simulate perspective
1076
0
    penWidth = 3.0;
1077
0
    bondColor.alpha = 1.0;
1078
1079
0
    if (fabs(zScale) > 1.0e-1) {
1080
0
      double beginAtomScale = (beginAtom->GetZ() - zMin) / zScale;
1081
0
      double endAtomScale = (endAtom->GetZ() - zMin) / zScale;
1082
0
      double averageScale = (beginAtomScale + endAtomScale)/2.0;
1083
0
      if (averageScale < 0.15)
1084
0
        averageScale = 0.15;
1085
1086
0
      penWidth = 3.0 * averageScale;
1087
0
      bondColor.alpha = averageScale;
1088
0
    }
1089
0
    painter->SetPenWidth(penWidth);
1090
1091
0
    if (order == 1) {
1092
0
      painter->DrawLine(begin.x(), begin.y(), end.x(), end.y());
1093
0
    } else if (order == 2) {
1094
0
      const vector3 orthogonalLine = cross(end - begin, VZ).normalize();
1095
1096
        // style1
1097
        //
1098
        // -----------
1099
        // -----------
1100
0
        const vector3 offset = orthogonalLine * 0.5 * bondSpacing;
1101
0
        painter->DrawLine(begin.x() + offset.x(), begin.y() + offset.y(),
1102
0
                            end.x() + offset.x(), end.y() + offset.y());
1103
0
        painter->DrawLine(begin.x() - offset.x(), begin.y() - offset.y(),
1104
0
                            end.x() - offset.x(), end.y() - offset.y());
1105
0
    } else if (order == 3) {
1106
0
      const vector3 orthogonalLine = cross(end - begin, VZ).normalize();
1107
0
      const vector3 offset = orthogonalLine * 0.7 * bondSpacing;
1108
0
      painter->DrawLine(begin.x(), begin.y(), end.x(), end.y());
1109
0
      painter->DrawLine(begin.x() + offset.x(), begin.y() + offset.y(),
1110
0
                        end.x() + offset.x(), end.y() + offset.y());
1111
0
      painter->DrawLine(begin.x() - offset.x(), begin.y() - offset.y(),
1112
0
                        end.x() - offset.x(), end.y() - offset.y());
1113
0
    }
1114
1115
0
  }
1116
1117
  inline void OBDepictPrivateBallAndStick::DrawWedge(OBAtom* beginAtom,
1118
  OBAtom* endAtom)
1119
0
  {
1120
0
    const vector3 begin = beginAtom->GetVector();
1121
0
    const vector3 end = endAtom->GetVector();
1122
0
    const vector3 vb = end - begin;
1123
0
    const vector3 orthogonalLine = cross(vb, VZ).normalize() * 0.5 * bondWidth;
1124
0
    std::vector<std::pair<double,double> > points;
1125
1126
0
    points.push_back(std::pair<double,double>(begin.x(), begin.y()));
1127
0
    points.push_back(std::pair<double,double>(end.x() + orthogonalLine.x(),
1128
0
                                              end.y() + orthogonalLine.y()));
1129
0
    points.push_back(std::pair<double,double>(end.x() - orthogonalLine.x(),
1130
0
                                              end.y() - orthogonalLine.y()));
1131
0
    painter->DrawPolygon(points);
1132
1133
0
  }
1134
1135
  inline void OBDepictPrivateBallAndStick::DrawHash(OBAtom* beginAtom,
1136
  OBAtom* endAtom)
1137
0
  {
1138
0
    const vector3 begin = beginAtom->GetVector();
1139
0
    const vector3 end = endAtom->GetVector();
1140
0
    const vector3 vb = end - begin;
1141
0
    const vector3 orthogonalLine = cross(vb, VZ).normalize() * 0.5 * bondWidth;
1142
0
    const double lines[6] = { 0.20, 0.36, 0.52, 0.68, 0.84, 1.0 };
1143
0
    const double oldwidth = painter->GetPenWidth();
1144
0
    painter->SetPenWidth(1);
1145
0
    for (int k = 0; k < 6; ++k) {
1146
0
      const double w = lines[k];
1147
0
      painter->DrawLine(begin.x() + vb.x() * w + orthogonalLine.x() * w,
1148
0
                        begin.y() + vb.y() * w + orthogonalLine.y() * w,
1149
0
                        begin.x() + vb.x() * w - orthogonalLine.x() * w,
1150
0
                        begin.y() + vb.y() * w - orthogonalLine.y() * w);
1151
0
    }
1152
0
    painter->SetPenWidth(oldwidth);
1153
1154
0
  }
1155
1156
  inline void OBDepictPrivateBallAndStick::DrawWobblyBond(OBAtom* beginAtom,
1157
  OBAtom* endAtom)
1158
0
  {
1159
0
    DrawSimpleBond(beginAtom, endAtom, 1);
1160
0
  }
1161
1162
  inline void OBDepictPrivateBallAndStick::DrawRingBond(OBAtom* beginAtom,
1163
  OBAtom* endAtom, const vector3& center, int order)
1164
0
  {
1165
0
    OBDepictPrivate::DrawRingBond(beginAtom, endAtom, center, order);
1166
0
  }
1167
1168
1169
  inline void OBDepictPrivateBallAndStick::DrawRing(OBRing* ring,
1170
  OBBitVec& drawnBonds)
1171
0
  {
1172
0
    OBDepictPrivate::DrawRing(ring, drawnBonds);
1173
0
  }
1174
1175
inline void OBDepictPrivateBallAndStick::DrawAromaticRing(OBRing* ring,
1176
OBBitVec& drawnBonds)
1177
0
  {
1178
1179
0
    const std::vector<int> indexes = ring->_path;
1180
0
    const size_t ringSize = indexes.size();
1181
0
    vector3 center(VZero);
1182
0
    double maxdist =0.;
1183
0
    for (std::vector<int>::const_iterator l = indexes.begin(); l != indexes.end(); ++l) {
1184
0
      center += mol->GetAtom(*l)->GetVector();
1185
0
      maxdist = max(maxdist, GetAtomRadius(mol->GetAtom(*l)));
1186
0
    }
1187
0
    center /= ringSize;
1188
1189
0
    for (unsigned int l = 0; l < indexes.size(); ++l) {
1190
0
      OBAtom *prev  = mol->GetAtom(indexes[l]);
1191
0
      OBAtom *begin = mol->GetAtom(indexes[(l+1) % ringSize]);
1192
0
      OBAtom *end   = mol->GetAtom(indexes[(l+2) % ringSize]);
1193
0
      OBAtom *next  = mol->GetAtom(indexes[(l+3) % ringSize]);
1194
1195
0
      OBBond *ringBond = mol->GetBond(begin, end);
1196
1197
      // calculate the expected opacity and width
1198
      // to simulate perspective
1199
0
      penWidth = 3.0;
1200
0
      bondColor.alpha = 1.0;
1201
1202
0
      if (fabs(zScale) > 1.0e-1) {
1203
0
        double beginAtomScale = (begin->GetZ() - zMin) / zScale;
1204
0
        double endAtomScale = (end->GetZ() - zMin) / zScale;
1205
0
        double averageScale = (beginAtomScale + endAtomScale)/2.0;
1206
0
        if (averageScale < 0.15)
1207
0
          averageScale = 0.15;
1208
1209
0
        penWidth = 3.0 * averageScale;
1210
0
        bondColor.alpha = averageScale;
1211
0
      }
1212
0
      painter->SetPenWidth(penWidth);
1213
1214
0
      if((options & OBDepict::internalColor) && ringBond->HasData("color"))
1215
0
        painter->SetPenColor(OBColor(ringBond->GetData("color")->GetValue()));
1216
0
      else
1217
0
        painter->SetPenColor(bondColor);
1218
1219
0
      DrawAromaticRingBond(prev,begin, end, next, center, maxdist);
1220
0
      drawnBonds.SetBitOn(ringBond->GetId());
1221
0
    }
1222
0
  }
1223
1224
  inline void OBDepictPrivateBallAndStick::DrawAromaticRingBond(OBAtom *prevAtom, OBAtom *beginAtom, OBAtom *endAtom, OBAtom *nextAtom, const vector3 &center, double dist)
1225
0
  {
1226
0
    const vector3 prev  = prevAtom->GetVector();
1227
0
    const vector3 begin = beginAtom->GetVector();
1228
0
    const vector3 end   = endAtom->GetVector();
1229
0
    const vector3 next  = nextAtom->GetVector();
1230
1231
0
    const vector3 orthogonalLine = cross(end - begin, VZ).normalize();
1232
0
    const vector3 offset = orthogonalLine * 0.5 * bondSpacing;
1233
0
    painter->DrawLine(begin.x() - offset.x(), begin.y() - offset.y(),
1234
0
                      end.x() - offset.x(), end.y() - offset.y());
1235
1236
0
    static const float dashpattern[] = {5., 5.};
1237
0
    static const vector<double> pat = vector<double>(dashpattern,dashpattern + sizeof(dashpattern)/sizeof(double));
1238
0
    painter->DrawLine(begin.x() + offset.x(), begin.y() + offset.y(),
1239
0
                      end.x() + offset.x(), end.y() + offset.y(), pat);
1240
0
  }
1241
1242
  void OBDepictPrivateBallAndStick::DrawAtom(OBAtom *atom)
1243
0
  {
1244
0
    double r, g, b;
1245
0
    OBElements::GetRGB(atom->GetAtomicNum(), &r, &g, &b);
1246
0
    OBColor atomColor = OBColor(r, g, b);
1247
0
    double opacity = 1.0;
1248
0
    if (fabs(zScale) > 1.0e-1)
1249
0
      opacity = sqrt((atom->GetZ() - zMin) / zScale);
1250
0
    if (opacity < 0.2)
1251
0
      opacity = 0.2;
1252
1253
0
    painter->SetFillRadial(OBColor("white"),atomColor);
1254
0
    painter->DrawBall(atom->GetVector().x(), atom->GetVector().y(),GetAtomRadius(atom), opacity);
1255
0
  }
1256
1257
  double OBDepictPrivateBallAndStick::GetAtomRadius(OBAtom *atom)
1258
0
  {
1259
0
    double radius = OBElements::GetCovalentRad(atom->GetAtomicNum());
1260
0
    double perspective = 1.0;
1261
0
    if (fabs(zScale) > 1.0e-1)
1262
0
      perspective = (atom->GetZ() - zMin) / zScale;
1263
0
    if (perspective < 0.5)
1264
0
      perspective = 0.5;
1265
1266
0
    return perspective * radius * bondLength / 1.1;
1267
0
  }
1268
1269
  void OBDepictPrivateBallAndStick::DrawAtomLabel(const std::string &label, int alignment, const vector3 &pos)
1270
0
    {
1271
0
      if (m_symbolOnBall)
1272
0
        OBDepictPrivate::DrawAtomLabel(label,alignment,pos);
1273
0
    }
1274
1275
}
1276
1277
/// @file depict.cpp
1278
/// @brief 2D depiction of molecules using OBPainter.