/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 ¢er, 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 ¢er, 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 ¢er, 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 ¢er, 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 ¢er, 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. |