/src/openbabel/src/formats/mol2format.cpp
Line | Count | Source |
1 | | /********************************************************************** |
2 | | Copyright (C) 1998-2001 by OpenEye Scientific Software, Inc. |
3 | | Some portions Copyright (C) 2001-2007 by Geoffrey R. Hutchison |
4 | | Some portions Copyright (C) 2004 by Chris Morley |
5 | | |
6 | | This program is free software; you can redistribute it and/or modify |
7 | | it under the terms of the GNU General Public License as published by |
8 | | the Free Software Foundation version 2 of the License. |
9 | | |
10 | | This program is distributed in the hope that it will be useful, |
11 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | | GNU General Public License for more details. |
14 | | ***********************************************************************/ |
15 | | #include <openbabel/babelconfig.h> |
16 | | |
17 | | #include <openbabel/obmolecformat.h> |
18 | | #include <openbabel/mol.h> |
19 | | #include <openbabel/atom.h> |
20 | | #include <openbabel/bond.h> |
21 | | #include <openbabel/obiter.h> |
22 | | #include <openbabel/elements.h> |
23 | | #include <openbabel/generic.h> |
24 | | #include <openbabel/kekulize.h> |
25 | | #include <openbabel/obfunctions.h> |
26 | | #include <openbabel/data.h> |
27 | | #include <cstdlib> |
28 | | |
29 | | using namespace std; |
30 | | namespace OpenBabel |
31 | | { |
32 | | //The routine WriteSmiOrderedMol2() in the original mol2.cpp is presumably |
33 | | //another output format, but was not made available in version 100.1.2, nor |
34 | | //is it here. |
35 | | |
36 | | class MOL2Format : public OBMoleculeFormat |
37 | | { |
38 | | public: |
39 | | //Register this format type ID |
40 | | MOL2Format() |
41 | 12 | { |
42 | 12 | OBConversion::RegisterFormat("mol2",this, "chemical/x-mol2"); |
43 | 12 | OBConversion::RegisterFormat("ml2",this); |
44 | 12 | OBConversion::RegisterFormat("sy2",this); |
45 | 12 | OBConversion::RegisterOptionParam("c", this, 0, OBConversion::INOPTIONS); |
46 | 12 | OBConversion::RegisterOptionParam("c", this, 0, OBConversion::OUTOPTIONS); |
47 | 12 | OBConversion::RegisterOptionParam("l", this, 0, OBConversion::OUTOPTIONS); |
48 | 12 | OBConversion::RegisterOptionParam("u", this, 0, OBConversion::OUTOPTIONS); |
49 | 12 | } |
50 | | |
51 | | const char* Description() override // required |
52 | 0 | { |
53 | 0 | return |
54 | 0 | "Sybyl Mol2 format\n" |
55 | 0 | "Read Options e.g. -ac\n" |
56 | 0 | " c Read UCSF Dock scores saved in comments preceding molecules\n\n" |
57 | 0 | "Write Options e.g. -xl\n" |
58 | 0 | " l Output ignores residue information (only ligands)\n" |
59 | 0 | " c Write UCSF Dock scores saved in comments preceding molecules\n" |
60 | 0 | " u Do not write formal charge information in UNITY records\n\n"; |
61 | 0 | } |
62 | | |
63 | | const char* SpecificationURL() override |
64 | 0 | { |
65 | 0 | return "http://www.tripos.com/data/support/mol2.pdf"; // XXX dead |
66 | 0 | } |
67 | | |
68 | | const char* GetMIMEType() override |
69 | 0 | { return "chemical/x-mol2"; } |
70 | | |
71 | | int SkipObjects(int n, OBConversion* pConv) override; |
72 | | |
73 | | //*** This section identical for most OBMol conversions *** |
74 | | //////////////////////////////////////////////////// |
75 | | /// The "API" interface functions |
76 | | bool ReadMolecule(OBBase* pOb, OBConversion* pConv) override; |
77 | | bool WriteMolecule(OBBase* pOb, OBConversion* pConv) override; |
78 | | }; |
79 | | //*** |
80 | | |
81 | | //Make an instance of the format class |
82 | | MOL2Format theMOL2Format; |
83 | | |
84 | | // Helper function for ReadMolecule |
85 | | // \return Is this atom a sulfur in a (di)thiocarboxyl (-CS2, -COS, CS2H or COSH) group? |
86 | | static bool IsThiocarboxylSulfur(OBAtom* queryatom) |
87 | 0 | { |
88 | 0 | if (queryatom->GetAtomicNum() != OBElements::Sulfur) |
89 | 0 | return(false); |
90 | 0 | if (queryatom->GetHvyDegree() != 1) |
91 | 0 | return(false); |
92 | 0 |
|
93 | 0 | OBAtom *atom = nullptr; |
94 | 0 | OBBond *bond; |
95 | 0 | OBBondIterator i; |
96 | 0 |
|
97 | 0 | for (bond = queryatom->BeginBond(i); bond; bond = queryatom->NextBond(i)) |
98 | 0 | if ((bond->GetNbrAtom(queryatom))->GetAtomicNum() == OBElements::Carbon) |
99 | 0 | { |
100 | 0 | atom = bond->GetNbrAtom(queryatom); |
101 | 0 | break; |
102 | 0 | } |
103 | 0 | if (!atom) |
104 | 0 | return(false); |
105 | 0 | if (!(atom->CountFreeSulfurs() == 2) |
106 | 0 | && !(atom->CountFreeOxygens() == 1 && atom->CountFreeSulfurs() == 1)) |
107 | 0 | return(false); |
108 | 0 |
|
109 | 0 | //atom is connected to a carbon that has a total |
110 | 0 | //of 2 attached free sulfurs or 1 free oxygen and 1 free sulfur |
111 | 0 | return(true); |
112 | 0 | } |
113 | | |
114 | | static bool IsOxygenOrSulfur(OBAtom *atom) |
115 | 0 | { |
116 | 0 | switch (atom->GetAtomicNum()) { |
117 | 0 | case 8: case 16: return true; |
118 | 0 | default: return false; |
119 | 0 | } |
120 | 0 | } |
121 | | |
122 | | static unsigned int GetAtomicNumAndIsotope(const char* symbol, int *isotope) |
123 | 0 | { |
124 | 0 | const char* p = symbol; |
125 | 0 | switch (p[0]) { |
126 | 0 | case 'D': |
127 | 0 | if (p[1] == '\0') { |
128 | 0 | *isotope = 2; |
129 | 0 | return 1; |
130 | 0 | } |
131 | 0 | break; |
132 | 0 | case 'T': |
133 | 0 | if (p[1] == '\0') { |
134 | 0 | *isotope = 3; |
135 | 0 | return 1; |
136 | 0 | } |
137 | 0 | break; |
138 | 0 | } |
139 | 0 | return OBElements::GetAtomicNum(symbol); |
140 | 0 | } |
141 | | |
142 | | //read from ifs until next rti is found and return it |
143 | | static string read_until_rti(istream & ifs) |
144 | 0 | { |
145 | 0 | char buffer[BUFF_SIZE]; |
146 | 0 | for (;;) |
147 | 0 | { |
148 | 0 | if (!ifs.getline(buffer,BUFF_SIZE)) |
149 | 0 | return ""; |
150 | 0 | if (!strncmp(buffer,"@<TRIPOS>",9)) |
151 | 0 | return string(buffer); |
152 | 0 | } |
153 | 0 | } |
154 | | ///////////////////////////////////////////////////////////////// |
155 | | bool MOL2Format::ReadMolecule(OBBase* pOb, OBConversion* pConv) |
156 | 0 | { |
157 | |
|
158 | 0 | OBMol* pmol = pOb->CastAndClear<OBMol>(); |
159 | 0 | if (pmol == nullptr) |
160 | 0 | return false; |
161 | | |
162 | | //Define some references so we can use the old parameter names |
163 | 0 | istream &ifs = *pConv->GetInStream(); |
164 | 0 | OBMol &mol = *pmol; |
165 | | |
166 | | //Old code follows... |
167 | 0 | bool foundAtomLine = false; |
168 | 0 | char buffer[BUFF_SIZE]; |
169 | 0 | char *comment = nullptr; |
170 | 0 | string str,str1; |
171 | 0 | vector<string> vstr; |
172 | 0 | int len; |
173 | | |
174 | | // Prevent reperception |
175 | 0 | mol.SetChainsPerceived(); |
176 | |
|
177 | 0 | mol.BeginModify(); |
178 | |
|
179 | 0 | for (;;) |
180 | 0 | { |
181 | 0 | if (!ifs.getline(buffer,BUFF_SIZE)) |
182 | 0 | return(false); |
183 | 0 | if (pConv->IsOption("c", OBConversion::INOPTIONS) != nullptr && EQn(buffer, "###########", 10)) |
184 | 0 | { |
185 | 0 | char attr[32], val[32]; |
186 | | // CVE-2022-43607: width-limit both specifiers to the buffer size. |
187 | 0 | sscanf(buffer, "########## %31[^:]:%31s", attr, val); |
188 | 0 | OBPairData *dd = new OBPairData; |
189 | 0 | dd->SetAttribute(attr); |
190 | 0 | dd->SetValue(val); |
191 | 0 | dd->SetOrigin(fileformatInput); |
192 | 0 | mol.SetData(dd); |
193 | 0 | } |
194 | 0 | if (EQn(buffer,"@<TRIPOS>MOLECULE",17)) |
195 | 0 | break; |
196 | 0 | } |
197 | | |
198 | | // OK, just read MOLECULE line |
199 | 0 | int lcount; |
200 | 0 | int natoms = 0, nbonds = 0; |
201 | 0 | bool hasPartialCharges = true; |
202 | 0 | for (lcount=0;;lcount++) |
203 | 0 | { |
204 | 0 | if (!ifs.getline(buffer,BUFF_SIZE)) |
205 | 0 | return(false); |
206 | 0 | if (EQn(buffer,"@<TRIPOS>ATOM",13)) |
207 | 0 | { |
208 | 0 | foundAtomLine = true; |
209 | 0 | break; |
210 | 0 | } |
211 | | |
212 | 0 | if (lcount == 0) |
213 | 0 | { |
214 | 0 | tokenize(vstr,buffer); |
215 | 0 | if (!vstr.empty()) |
216 | 0 | mol.SetTitle(buffer); |
217 | 0 | } |
218 | 0 | else if (lcount == 1) |
219 | 0 | sscanf(buffer,"%d%d",&natoms,&nbonds); |
220 | 0 | else if (lcount == 3) // charge descriptions |
221 | 0 | { |
222 | | // Annotate origin of partial charges |
223 | 0 | OBPairData *dp = new OBPairData; |
224 | 0 | dp->SetAttribute("PartialCharges"); |
225 | 0 | dp->SetValue(buffer); |
226 | 0 | dp->SetOrigin(fileformatInput); |
227 | 0 | mol.SetData(dp); |
228 | |
|
229 | 0 | if (strncasecmp(buffer, "NO_CHARGES", 10) == 0) |
230 | 0 | hasPartialCharges = false; |
231 | 0 | } |
232 | 0 | else if (lcount == 4) //energy (?) |
233 | 0 | { |
234 | 0 | tokenize(vstr,buffer); |
235 | 0 | if (!vstr.empty() && vstr.size() == 3) |
236 | 0 | if (vstr[0] == "Energy") |
237 | 0 | mol.SetEnergy(atof(vstr[2].c_str())); |
238 | 0 | } |
239 | 0 | else if (lcount == 5) //comment |
240 | 0 | { |
241 | 0 | if ( buffer[0] ) |
242 | 0 | { |
243 | 0 | len = (int) strlen(buffer)+1; |
244 | | //! @todo allow better multi-line comments |
245 | | // which don't allow ill-formed data to consume memory |
246 | | // Thanks to Andrew Dalke for the pointer |
247 | 0 | delete [] comment; |
248 | 0 | comment = new char [len]; |
249 | 0 | memcpy(comment,buffer,len); |
250 | 0 | } |
251 | 0 | } |
252 | 0 | } |
253 | | |
254 | 0 | if (!foundAtomLine) |
255 | 0 | { |
256 | 0 | mol.EndModify(); |
257 | 0 | mol.Clear(); |
258 | 0 | obErrorLog.ThrowError(__FUNCTION__, "Unable to read Mol2 format file. No atoms found.", obWarning); |
259 | 0 | return(false); |
260 | 0 | } |
261 | | |
262 | 0 | if (natoms < 0 || natoms >= 100000000 || nbonds < 0 || nbonds >= 100000000) |
263 | 0 | { |
264 | 0 | mol.EndModify(); |
265 | 0 | mol.Clear(); |
266 | 0 | obErrorLog.ThrowError(__FUNCTION__, "Unable to read Mol2 format file. Invalid atom or bond count.", obWarning); |
267 | 0 | return(false); |
268 | 0 | } |
269 | | |
270 | 0 | mol.ReserveAtoms(natoms); |
271 | |
|
272 | 0 | int i; |
273 | 0 | vector3 v; |
274 | 0 | OBAtom atom; |
275 | 0 | double x,y,z,pcharge; |
276 | 0 | char temp_type[BUFF_SIZE], resname[BUFF_SIZE], atmid[BUFF_SIZE]; |
277 | 0 | int elemno, resnum = -1; |
278 | 0 | int isotope = 0; |
279 | 0 | bool has_explicit_hydrogen = false; |
280 | 0 | bool has_residue_information = false; |
281 | |
|
282 | 0 | ttab.SetFromType("SYB"); |
283 | 0 | for (i = 0;i < natoms;i++) |
284 | 0 | { |
285 | 0 | if (!ifs.getline(buffer,BUFF_SIZE)) |
286 | 0 | return(false); |
287 | | // Required ATOM columns are the atom name, x/y/z and the SYBYL atom |
288 | | // type. subst_id, subst_name and charge are optional, so reset them |
289 | | // to their defaults each iteration (these locals are declared outside |
290 | | // the loop and would otherwise carry over from the previous atom). |
291 | 0 | resnum = -1; |
292 | 0 | resname[0] = '\0'; |
293 | 0 | pcharge = 0.0; |
294 | 0 | isotope = 0; |
295 | 0 | if (sscanf(buffer," %*s %1024s %lf %lf %lf %1024s %d %1024s %lf", |
296 | 0 | atmid, &x,&y,&z, temp_type, &resnum, resname, &pcharge) < 5) |
297 | 0 | { |
298 | 0 | obErrorLog.ThrowError(__FUNCTION__, |
299 | 0 | "Unable to read Mol2 format file. Truncated " |
300 | 0 | "atom record.", obWarning); |
301 | 0 | return(false); |
302 | 0 | } |
303 | | |
304 | 0 | atom.SetVector(x, y, z); |
305 | 0 | atom.SetFormalCharge(0); |
306 | | |
307 | | // Handle "CL" and "BR" and other mis-typed atoms |
308 | 0 | str = temp_type; |
309 | 0 | if (strncmp(temp_type, "CL", 2) == 0) { |
310 | 0 | str = "Cl"; |
311 | 0 | } else if (strncmp(temp_type,"BR",2) == 0) { |
312 | 0 | str = "Br"; |
313 | 0 | } else if (strncmp(temp_type,"S.o2", 4) == 0) { |
314 | 0 | str = "S.O2"; |
315 | 0 | } else if (strncmp(temp_type,"S.o", 3) == 0) { |
316 | 0 | str = "S.O"; |
317 | 0 | } else if (strncmp(temp_type,"SI", 2) == 0) { |
318 | 0 | str = "Si"; |
319 | | // The following cases are entries which are not in openbabel/data/types.txt |
320 | | // and should probably be added there |
321 | 0 | } else if (strncmp(temp_type,"S.1", 3) == 0) { |
322 | 0 | str = "S.2"; // no idea what the best type might be here |
323 | 0 | } else if (strncmp(temp_type,"P.", 2) == 0) { |
324 | 0 | str = "P.3"; |
325 | 0 | } else if (strncasecmp(temp_type,"Ti.", 3) == 0) { // e.g. Ti.th |
326 | 0 | str = "Ti"; |
327 | 0 | } else if (strncasecmp(temp_type,"Ru.", 3) == 0) { // e.g. Ru.oh |
328 | 0 | str = "Ru"; |
329 | | // Fixes PR#3557898 |
330 | 0 | } else if (strncmp(temp_type, "N.4", 3) == 0) { |
331 | 0 | atom.SetFormalCharge(1); |
332 | 0 | } |
333 | |
|
334 | 0 | ttab.SetToType("ATN"); |
335 | 0 | ttab.Translate(str1,str); |
336 | 0 | elemno = atoi(str1.c_str()); |
337 | 0 | ttab.SetToType("IDX"); |
338 | | |
339 | | // We might have missed some SI or FE type things above, so here's |
340 | | // another check |
341 | 0 | if( !elemno && isupper(temp_type[1]) ) |
342 | 0 | { |
343 | 0 | temp_type[1] = (char)tolower(temp_type[1]); |
344 | 0 | str = temp_type; |
345 | 0 | ttab.SetToType("ATN"); |
346 | 0 | ttab.Translate(str1,str); |
347 | 0 | elemno = atoi(str1.c_str()); |
348 | 0 | ttab.SetToType("IDX"); |
349 | 0 | } |
350 | | // One last check if there isn't a period in the type, |
351 | | // it's a malformed atom type, but it may be the element symbol |
352 | | // GaussView does this (PR#1739905) |
353 | 0 | if ( !elemno ) { |
354 | | // check if it's "Du" or "Xx" and the element is in the atom name |
355 | 0 | if (str == "Du" || str == "Xx") { |
356 | 0 | str = atmid; |
357 | 0 | for (unsigned int i = 0; i < str.length(); ++i) |
358 | 0 | if (!isalpha(str[i])) { |
359 | 0 | str.erase(i); |
360 | 0 | break; // we've erased the end of the string |
361 | 0 | } |
362 | 0 | } |
363 | | |
364 | |
|
365 | 0 | std::stringstream errorMsg; |
366 | 0 | errorMsg << "This Mol2 file is non-standard. Problem with molecule: " |
367 | 0 | << mol.GetTitle() |
368 | 0 | << " Cannot interpret atom types correctly, instead attempting to interpret atom type: " |
369 | 0 | << str << " as elements instead."; |
370 | 0 | obErrorLog.ThrowError(__FUNCTION__, errorMsg.str(), obWarning); |
371 | |
|
372 | 0 | string::size_type dotPos = str.find('.'); |
373 | 0 | if (dotPos == string::npos) { |
374 | 0 | elemno = GetAtomicNumAndIsotope(str.c_str(), &isotope); |
375 | 0 | } |
376 | 0 | } |
377 | |
|
378 | 0 | atom.SetAtomicNum(elemno); |
379 | 0 | if (isotope) |
380 | 0 | atom.SetIsotope(isotope); |
381 | 0 | else if (elemno == 1) |
382 | 0 | has_explicit_hydrogen = true; |
383 | 0 | ttab.SetToType("INT"); |
384 | 0 | ttab.Translate(str1,str); |
385 | 0 | atom.SetType(str1); |
386 | 0 | atom.SetPartialCharge(pcharge); |
387 | | // MMFF94 has different atom types for Cu(I) and Cu(II) |
388 | | // as well as for Fe(II) and Fe(III), so the correct formal |
389 | | // charge is needed for correct atom type assignment |
390 | 0 | if (str1 == "Cu" || str1 == "Fe") |
391 | 0 | atom.SetFormalCharge((int)pcharge); |
392 | 0 | if (!mol.AddAtom(atom)) |
393 | 0 | return(false); |
394 | 0 | if (fabs(pcharge) >= 2e-6) |
395 | 0 | hasPartialCharges = true; |
396 | | |
397 | | // Add residue information if it exists |
398 | 0 | if (resnum != -1 && resnum != 0 && |
399 | 0 | strlen(resname) != 0 && strncmp(resname,"<1>", 3) != 0) |
400 | 0 | { |
401 | 0 | has_residue_information = true; |
402 | 0 | OBResidue *res = (mol.NumResidues() > 0) ? |
403 | 0 | mol.GetResidue(mol.NumResidues()-1) : nullptr; |
404 | 0 | if (res == nullptr || res->GetName() != resname || |
405 | 0 | res->GetNum() != resnum) |
406 | 0 | { |
407 | 0 | vector<OBResidue*>::iterator ri; |
408 | 0 | for (res = mol.BeginResidue(ri) ; res ; res = mol.NextResidue(ri)) |
409 | 0 | if (res->GetName() == resname && |
410 | 0 | res->GetNum() == resnum) |
411 | 0 | break; |
412 | |
|
413 | 0 | if (res == nullptr) |
414 | 0 | { |
415 | 0 | res = mol.NewResidue(); |
416 | 0 | res->SetName(resname); |
417 | 0 | res->SetNum(resnum); |
418 | 0 | } |
419 | 0 | } |
420 | 0 | OBAtom *atomPtr = mol.GetAtom(mol.NumAtoms()); |
421 | 0 | res->AddAtom(atomPtr); |
422 | 0 | res->SetAtomID(atomPtr, atmid); |
423 | 0 | } // end adding residue info |
424 | 0 | } |
425 | | |
426 | 0 | string nextrti; |
427 | 0 | do { nextrti = read_until_rti(ifs); } |
428 | 0 | while(nextrti != "@<TRIPOS>UNITY_ATOM_ATTR" && nextrti != "@<TRIPOS>BOND" && nextrti.length() > 0); |
429 | |
|
430 | 0 | if(nextrti == "@<TRIPOS>UNITY_ATOM_ATTR") |
431 | 0 | { //read in formal charge information, must be done before Kekulization |
432 | 0 | int aid = 0, num = 0; |
433 | 0 | while (ifs.peek() != '@' && ifs.getline(buffer,BUFF_SIZE)) |
434 | 0 | { |
435 | | // Reset before each parse so a malformed header line cannot reuse |
436 | | // the previous entry's atom id / attribute count. |
437 | 0 | aid = 0; |
438 | 0 | num = 0; |
439 | 0 | if (sscanf(buffer,"%d %d",&aid, &num) < 2) |
440 | 0 | continue; |
441 | 0 | for(int i = 0; i < num; i++) |
442 | 0 | { |
443 | 0 | if (!ifs.getline(buffer,BUFF_SIZE)) |
444 | 0 | return(false); |
445 | 0 | if(strncmp(buffer, "charge", 6) == 0) |
446 | 0 | { |
447 | 0 | int charge = 0; |
448 | 0 | sscanf(buffer,"%*s %d",&charge); |
449 | 0 | if(aid >= 1 && aid <= (int)mol.NumAtoms()) |
450 | 0 | { |
451 | 0 | OBAtom *atom = mol.GetAtom(aid); |
452 | 0 | if (atom != nullptr) |
453 | 0 | atom->SetFormalCharge(charge); |
454 | 0 | } |
455 | 0 | } |
456 | 0 | } |
457 | 0 | } |
458 | 0 | } |
459 | | |
460 | 0 | while(nextrti != "@<TRIPOS>BOND" && nextrti.length() > 0) |
461 | 0 | nextrti = read_until_rti(ifs); |
462 | |
|
463 | 0 | if(nextrti != "@<TRIPOS>BOND") |
464 | 0 | return false; |
465 | | |
466 | 0 | int start, end; |
467 | 0 | bool needs_kekulization = false; |
468 | 0 | for (i = 0; i < nbonds; i++) |
469 | 0 | { |
470 | 0 | if (!ifs.getline(buffer,BUFF_SIZE)) |
471 | 0 | return(false); |
472 | | |
473 | | // origin/target atom ids and the bond type are all required. |
474 | 0 | temp_type[0] = '\0'; |
475 | 0 | if (sscanf(buffer,"%*d %d %d %1024s",&start,&end,temp_type) < 3) |
476 | 0 | { |
477 | 0 | obErrorLog.ThrowError(__FUNCTION__, |
478 | 0 | "Unable to read Mol2 format file. Truncated " |
479 | 0 | "bond record.", obWarning); |
480 | 0 | return(false); |
481 | 0 | } |
482 | 0 | str = temp_type; |
483 | 0 | unsigned int flags = 0; |
484 | 0 | int order; |
485 | 0 | if (str == "ar" || str == "AR" || str == "Ar") { |
486 | 0 | order = 1; |
487 | 0 | flags = OB_AROMATIC_BOND; |
488 | 0 | needs_kekulization = true; |
489 | 0 | } |
490 | 0 | else if (str == "AM" || str == "am" || str == "Am") |
491 | 0 | order = 1; |
492 | 0 | else |
493 | 0 | order = atoi(str.c_str()); |
494 | |
|
495 | 0 | mol.AddBond(start, end, order, flags); |
496 | 0 | } |
497 | | |
498 | | // TODO: Add a test case for the statement below of Paolo Tosco |
499 | | // - I am currently assuming that is not a problem for the |
500 | | // the current kekulization code, but it needs to be |
501 | | // checked |
502 | | // "Make a pass to ensure that there are no double bonds |
503 | | // between atoms which are also involved in aromatic bonds |
504 | | // as that may ill-condition kekulization (fixes potential |
505 | | // issues with molecules like CEWYIM30 (MMFF94 validation suite)" |
506 | | |
507 | 0 | mol.SetAromaticPerceived(); // don't trigger reperception |
508 | |
|
509 | 0 | if (has_explicit_hydrogen) { |
510 | 0 | FOR_ATOMS_OF_MOL(atom, mol) { |
511 | 0 | unsigned int total_valence = atom->GetTotalDegree(); |
512 | 0 | switch (atom->GetAtomicNum()) { |
513 | 0 | case 8: |
514 | 0 | if (total_valence != 1) continue; |
515 | 0 | if (strcmp(atom->GetType(), "O2") != 0) continue; // TODO: the O.co2 type is lost by this point |
516 | 0 | { |
517 | 0 | OBAtomBondIter bit(&*atom); |
518 | 0 | if (!bit->IsAromatic() && bit->GetBondOrder() == 1) |
519 | 0 | atom->SetFormalCharge(-1); // set -1 charge on dangling O.co2 |
520 | 0 | } |
521 | 0 | break; |
522 | 0 | case 17: // Cl |
523 | 0 | if (total_valence == 0) |
524 | 0 | atom->SetFormalCharge(-1); |
525 | 0 | break; |
526 | 0 | } |
527 | 0 | } |
528 | 0 | } |
529 | | |
530 | | // Kekulization is necessary if an aromatic bond is present |
531 | 0 | if (needs_kekulization) { |
532 | | // "de-aromatize" carboxylates and (di)thiocarboxylates |
533 | | // The typical case (in our test suite anyway) is a carboxylate binding to |
534 | | // a metal ion. The two O's have charges of -0.5 and the bond orders are aromatic |
535 | 0 | FOR_ATOMS_OF_MOL(atom, mol) { |
536 | 0 | OBAtom* oxygenOrSulfur = &*atom; |
537 | | // Look first for a terminal O/S |
538 | 0 | if (!IsOxygenOrSulfur(oxygenOrSulfur) || oxygenOrSulfur->GetTotalDegree() != 1) continue; |
539 | 0 | OBAtomBondIter bitA(oxygenOrSulfur); |
540 | 0 | OBBond *bondA = &*bitA; |
541 | 0 | if (!bondA->IsAromatic()) continue; |
542 | | // Look for the carbon |
543 | 0 | OBAtom *carbon = bondA->GetNbrAtom(oxygenOrSulfur); |
544 | 0 | if (carbon->GetAtomicNum() != 6) continue; |
545 | | // Look for the other oxygen or sulfur |
546 | 0 | OBAtom* otherOxygenOrSulfur = nullptr; |
547 | 0 | OBBond* bondB = nullptr; |
548 | 0 | FOR_BONDS_OF_ATOM(bitB, carbon) { |
549 | 0 | if (&*bitB == bondA || !bitB->IsAromatic()) continue; |
550 | 0 | OBAtom* nbr = bitB->GetNbrAtom(carbon); |
551 | 0 | if (IsOxygenOrSulfur(nbr) && nbr->GetTotalDegree() == 1) { |
552 | 0 | otherOxygenOrSulfur = nbr; |
553 | 0 | bondB = &*bitB; |
554 | 0 | } |
555 | 0 | } |
556 | 0 | if (!otherOxygenOrSulfur) continue; |
557 | 0 | if(otherOxygenOrSulfur->GetFormalCharge() != 0) continue; //formal charge already set on one |
558 | | |
559 | | // Now set as C(=O)O |
560 | 0 | bondA->SetAromatic(false); |
561 | 0 | oxygenOrSulfur->SetFormalCharge(-1); |
562 | |
|
563 | 0 | bondB->SetAromatic(false); |
564 | 0 | bondB->SetBondOrder(2); |
565 | 0 | } |
566 | | |
567 | | // First of all, set the atoms at the ends of the aromatic bonds to also |
568 | | // be aromatic. This information is required for OBKekulize. |
569 | 0 | FOR_BONDS_OF_MOL(bond, mol) { |
570 | 0 | if (bond->IsAromatic()) { |
571 | 0 | bond->GetBeginAtom()->SetAromatic(); |
572 | 0 | bond->GetEndAtom()->SetAromatic(); |
573 | 0 | } |
574 | 0 | } |
575 | 0 | bool ok = OBKekulize(&mol); |
576 | 0 | if (!ok) { |
577 | 0 | stringstream errorMsg; |
578 | 0 | errorMsg << "Failed to kekulize aromatic bonds in MOL2 file"; |
579 | 0 | std::string title = mol.GetTitle(); |
580 | 0 | if (!title.empty()) |
581 | 0 | errorMsg << " (title is " << title << ")"; |
582 | 0 | errorMsg << endl; |
583 | 0 | obErrorLog.ThrowError(__FUNCTION__, errorMsg.str(), obWarning); |
584 | | // return false; Should we return false for a kekulization failure? |
585 | 0 | } |
586 | 0 | } |
587 | |
|
588 | 0 | mol.EndModify(); |
589 | | |
590 | | // Suggestion by Liu Zhiguo 2008-01-26 |
591 | | // Mol2 files define atom types -- there is no need to re-perceive |
592 | 0 | mol.SetAtomTypesPerceived(); |
593 | |
|
594 | 0 | if (has_residue_information) |
595 | 0 | mol.SetChainsPerceived(); |
596 | |
|
597 | 0 | if (!has_explicit_hydrogen) { |
598 | | // Guess how many hydrogens are present on each atom based on typical valencies |
599 | | // TODO: implement the MOL2 valence model (if it exists) |
600 | 0 | FOR_ATOMS_OF_MOL(matom, mol) { |
601 | 0 | if (matom->GetImplicitHCount() == 0) |
602 | 0 | OBAtomAssignTypicalImplicitHydrogens(&*matom); |
603 | 0 | } |
604 | 0 | } |
605 | | |
606 | | //must add generic data after end modify - otherwise it will be blown away |
607 | 0 | if (comment) |
608 | 0 | { |
609 | 0 | OBCommentData *cd = new OBCommentData; |
610 | 0 | cd->SetData(comment); |
611 | 0 | cd->SetOrigin(fileformatInput); |
612 | 0 | mol.SetData(cd); |
613 | 0 | delete [] comment; |
614 | 0 | comment = nullptr; |
615 | 0 | } |
616 | 0 | if (hasPartialCharges) |
617 | 0 | mol.SetPartialChargesPerceived(); |
618 | | |
619 | | /* Disabled due to PR#3048758 -- seekg is very slow with gzipped mol2 |
620 | | // continue untill EOF or untill next molecule record |
621 | | streampos pos; |
622 | | for(;;) |
623 | | { |
624 | | pos = ifs.tellg(); |
625 | | if (!ifs.getline(buffer,BUFF_SIZE)) |
626 | | break; |
627 | | if (EQn(buffer,"@<TRIPOS>MOLECULE",17)) |
628 | | break; |
629 | | } |
630 | | |
631 | | ifs.seekg(pos); // go back to the end of the molecule |
632 | | */ |
633 | |
|
634 | 0 | return(true); |
635 | 0 | } |
636 | | |
637 | | //////////////////////////////////////////////////////////////// |
638 | | |
639 | | bool MOL2Format::WriteMolecule(OBBase* pOb, OBConversion* pConv) |
640 | 6.20k | { |
641 | 6.20k | OBMol* pmol = dynamic_cast<OBMol*>(pOb); |
642 | 6.20k | if (pmol == nullptr) |
643 | 0 | return false; |
644 | | |
645 | | //Define some references so we can use the old parameter names |
646 | 6.20k | ostream &ofs = *pConv->GetOutStream(); |
647 | 6.20k | OBMol &mol = *pmol; |
648 | 6.20k | bool ligandsOnly = pConv->IsOption("l", OBConversion::OUTOPTIONS) != nullptr; |
649 | 6.20k | bool skipFormalCharge = pConv->IsOption("u", OBConversion::OUTOPTIONS) != nullptr; |
650 | | |
651 | | //The old code follows.... |
652 | 6.20k | string str,str1; |
653 | 6.20k | char buffer[BUFF_SIZE],label[BUFF_SIZE]; |
654 | 6.20k | char rnum[BUFF_SIZE],rlabel[BUFF_SIZE]; |
655 | | |
656 | | //Check if UCSF Dock style coments are on |
657 | 6.20k | if (pConv->IsOption("c", OBConversion::OUTOPTIONS) != nullptr) { |
658 | 0 | vector<OBGenericData*>::iterator k; |
659 | 0 | vector<OBGenericData*> vdata = mol.GetData(); |
660 | 0 | ofs << endl; |
661 | 0 | for (k = vdata.begin();k != vdata.end();++k) { |
662 | 0 | if ((*k)->GetDataType() == OBGenericDataType::PairData |
663 | 0 | && (*k)->GetOrigin()!=local //internal OBPairData is not written |
664 | 0 | && (*k)->GetAttribute()!="PartialCharges") |
665 | 0 | { |
666 | 0 | ofs << "##########\t" << (*k)->GetAttribute() << ":\t" << ((OBPairData*)(*k))->GetValue() << endl; |
667 | 0 | } |
668 | 0 | } |
669 | 0 | ofs << endl; |
670 | 0 | } |
671 | | |
672 | 6.20k | ofs << "@<TRIPOS>MOLECULE" << endl; |
673 | 6.20k | str = mol.GetTitle(); |
674 | 6.20k | if (str.empty()) |
675 | 5.42k | ofs << "*****" << endl; |
676 | 780 | else |
677 | 780 | ofs << str << endl; |
678 | | |
679 | 6.20k | snprintf(buffer, BUFF_SIZE," %d %d 0 0 0", mol.NumAtoms(),mol.NumBonds()); |
680 | 6.20k | ofs << buffer << endl; |
681 | 6.20k | ofs << "SMALL" << endl; // TODO: detect if we have protein, biopolymer, etc. |
682 | | |
683 | 6.20k | OBPairData *dp = (OBPairData*)mol.GetData("PartialCharges"); |
684 | 6.20k | if (dp != nullptr) { |
685 | | // Tripos spec says: |
686 | | // NO_CHARGES, DEL_RE, GASTEIGER, GAST_HUCK, HUCKEL, PULLMAN, |
687 | | // GAUSS80_CHARGES, AMPAC_CHARGES, MULLIKEN_CHARGES, DICT_ CHARGES, |
688 | | // MMFF94_CHARGES, USER_CHARGES |
689 | 717 | if (strcasecmp(dp->GetValue().c_str(),"Mulliken") == 0) |
690 | 3 | ofs << "MULLIKEN_CHARGES" << endl; |
691 | 714 | else if (strcasecmp(dp->GetValue().c_str(),"MMFF94") == 0) |
692 | 3 | ofs << "MMFF94_CHARGES" << endl; |
693 | 711 | else if (strcasecmp(dp->GetValue().c_str(),"ESP") == 0) |
694 | 3 | ofs << "USER_CHARGES" << endl; |
695 | 708 | else if (strcasecmp(dp->GetValue().c_str(),"Gasteiger") == 0) |
696 | 3 | ofs << "GASTEIGER" << endl; |
697 | 705 | else // ideally, code should pick from the Tripos types |
698 | 705 | ofs << "USER_CHARGES" << endl; |
699 | 717 | } |
700 | 5.48k | else { // No idea what these charges are... all our code sets "PartialCharges" |
701 | 5.48k | ofs << "GASTEIGER" << endl; |
702 | 5.48k | } |
703 | | |
704 | | // ofs << "Energy = " << mol.GetEnergy() << endl; |
705 | | |
706 | 6.20k | if (mol.HasData(OBGenericDataType::CommentData)) |
707 | 0 | { |
708 | 0 | ofs << "****\n"; // comment line printed, so we need to add "no status bits set" |
709 | 0 | OBCommentData *cd = (OBCommentData*)mol.GetData(OBGenericDataType::CommentData); |
710 | 0 | ofs << cd->GetData(); |
711 | 0 | } |
712 | | |
713 | 6.20k | ofs << endl; |
714 | 6.20k | ofs << "@<TRIPOS>ATOM" << endl; |
715 | | |
716 | 6.20k | OBAtom *atom; |
717 | 6.20k | OBResidue *res; |
718 | | |
719 | 6.20k | vector<OBAtom*>::iterator i; |
720 | 6.20k | std::map<int, int> labelcount; |
721 | | |
722 | 6.20k | ttab.SetFromType("INT"); |
723 | 6.20k | ttab.SetToType("SYB"); |
724 | | |
725 | 6.20k | bool hasFormalCharges = false; |
726 | 6.20k | for (atom = mol.BeginAtom(i);atom;atom = mol.NextAtom(i)) |
727 | 0 | { |
728 | | |
729 | | // |
730 | | // Use sequentially numbered atom names if no residues |
731 | | // |
732 | |
|
733 | 0 | snprintf(label,BUFF_SIZE, "%s%d", |
734 | 0 | OBElements::GetSymbol(atom->GetAtomicNum()), |
735 | 0 | ++labelcount[atom->GetAtomicNum()]); |
736 | 0 | strcpy(rlabel,"<1>"); |
737 | 0 | strcpy(rnum,"1"); |
738 | |
|
739 | 0 | str = atom->GetType(); |
740 | 0 | ttab.Translate(str1,str); |
741 | |
|
742 | 0 | if (atom->GetFormalCharge() != 0) hasFormalCharges = true; |
743 | | // |
744 | | // Use original atom names if there are residues |
745 | | // |
746 | |
|
747 | 0 | if (!ligandsOnly && (res = atom->GetResidue()) ) |
748 | 0 | { |
749 | | // use original atom names defined by residue |
750 | 0 | snprintf(label,BUFF_SIZE,"%s",(char*)res->GetAtomID(atom).c_str()); |
751 | | // make sure that residue name includes its number |
752 | 0 | snprintf(rlabel,BUFF_SIZE,"%s%d",res->GetName().c_str(), res->GetNum()); |
753 | 0 | snprintf(rnum,BUFF_SIZE,"%d",res->GetNum()); |
754 | 0 | } |
755 | |
|
756 | 0 | snprintf(buffer,BUFF_SIZE,"%7d %-6s %9.4f %9.4f %9.4f %-5s %3s %-8s %9.4f", |
757 | 0 | atom->GetIdx(),label, |
758 | 0 | atom->GetX(),atom->GetY(),atom->GetZ(), |
759 | 0 | str1.c_str(), |
760 | 0 | rnum,rlabel, |
761 | 0 | atom->GetPartialCharge()); |
762 | 0 | ofs << buffer << endl; |
763 | 0 | } |
764 | | |
765 | | //store formal charge info; put before bonds so we don't have |
766 | | //to read past the end of the molecule to realize it is there |
767 | 6.20k | if(hasFormalCharges && !skipFormalCharge) { |
768 | | //dkoes - to enable roundtriping of charges |
769 | 0 | ofs << "@<TRIPOS>UNITY_ATOM_ATTR\n"; |
770 | 0 | for (atom = mol.BeginAtom(i);atom;atom = mol.NextAtom(i)) |
771 | 0 | { |
772 | 0 | int charge = atom->GetFormalCharge(); |
773 | 0 | if (charge != 0) |
774 | 0 | { |
775 | 0 | ofs << atom->GetIdx() << " 1\n"; //one attribute |
776 | 0 | ofs << "charge " << charge << "\n"; //namely charge |
777 | 0 | } |
778 | 0 | } |
779 | 0 | } |
780 | | |
781 | 6.20k | ofs << "@<TRIPOS>BOND" << endl; |
782 | 6.20k | OBBond *bond; |
783 | 6.20k | vector<OBBond*>::iterator j; |
784 | 6.20k | string s1, s2; |
785 | 6.20k | for (bond = mol.BeginBond(j);bond;bond = mol.NextBond(j)) |
786 | 0 | { |
787 | 0 | s1 = bond->GetBeginAtom()->GetType(); |
788 | 0 | s2 = bond->GetEndAtom()->GetType(); |
789 | 0 | if (bond->IsAromatic() || s1 == "O.co2" || s2 == "O.co2") |
790 | 0 | strcpy(label,"ar"); |
791 | 0 | else if (bond->IsAmide()) |
792 | 0 | strcpy(label,"am"); |
793 | 0 | else |
794 | 0 | snprintf(label,BUFF_SIZE,"%d",bond->GetBondOrder()); |
795 | |
|
796 | 0 | snprintf(buffer, BUFF_SIZE,"%6d %5d %5d %2s", |
797 | 0 | bond->GetIdx()+1,bond->GetBeginAtomIdx(),bond->GetEndAtomIdx(), |
798 | 0 | label); |
799 | 0 | ofs << buffer << endl; |
800 | 0 | } |
801 | | // NO trailing blank line (PR#1868929). |
802 | | // ofs << endl; |
803 | | |
804 | 6.20k | return(true); |
805 | 6.20k | } |
806 | | |
807 | | int MOL2Format::SkipObjects(int n, OBConversion* pConv) |
808 | 0 | { |
809 | 0 | const char txt[] = "@<TRIPOS>MOLECULE"; |
810 | 0 | istream& ifs = *pConv->GetInStream(); |
811 | 0 | if(!ifs) |
812 | 0 | return -1; |
813 | 0 | if(n>0 && ifs.peek()==txt[0]) |
814 | 0 | ifs.ignore(); // move past '@' so that next mol will be found |
815 | 0 | do { |
816 | 0 | ignore(ifs, txt); |
817 | 0 | } while(ifs && (--n)>0); |
818 | |
|
819 | 0 | if(!ifs.eof()) |
820 | 0 | ifs.seekg(1-sizeof(txt), ios::cur);//1 for '/0' |
821 | 0 | char ch = ifs.peek(); |
822 | 0 | return 1; |
823 | 0 | } |
824 | | |
825 | | } //namespace OpenBabel |