1# $Id$ 2# 3# Copyright (C) 2002-2006 Greg Landrum and Rational Discovery LLC 4# 5# @@ All Rights Reserved @@ 6# This file is part of the RDKit. 7# The contents are covered by the terms of the BSD license 8# which is included in the file license.txt, found at the root 9# of the RDKit source tree. 10# 11""" tools for interacting with chemdraw 12 13""" 14 15 16import tempfile, os, time 17try: 18 import pythoncom 19 from win32com.client import gencache, Dispatch, constants 20 import win32com.client.gencache 21 cdxModule = win32com.client.gencache.EnsureModule("{5F646AAB-3B56-48D2-904C-A68D7989C251}", 0, 7, 22 0) 23except Exception: 24 cdxModule = None 25 _cdxVersion = 0 26 raise ImportError("ChemDraw version (at least version 7) not found.") 27else: 28 _cdxVersion = 7 29 30if cdxModule: 31 from win32com.client import Dispatch 32 import win32gui 33import re 34 35cdApp = None 36theDoc = None 37theObjs = None 38selectItem = None 39cleanItem = None 40centerItem = None 41 42 43def StartChemDraw(visible=True, openDoc=False, showDoc=False): 44 """ launches chemdraw """ 45 global cdApp, theDoc, theObjs, selectItem, cleanItem, centerItem 46 if cdApp is not None: 47 # if called more than once, do a restart 48 holder = None 49 selectItem = None 50 cleanItem = None 51 centerItem = None 52 theObjs = None 53 theDoc = None 54 cdApp = None 55 56 cdApp = Dispatch('ChemDraw.Application') 57 if openDoc: 58 theDoc = cdApp.Documents.Add() 59 theObjs = theDoc.Objects 60 else: 61 theDoc = None 62 selectItem = cdApp.MenuBars(1).Menus(2).MenuItems(8) 63 cleanItem = cdApp.MenuBars(1).Menus(5).MenuItems(6) 64 if _cdxVersion == 6: 65 centerItem = cdApp.MenuBars(1).Menus(4).MenuItems(1) 66 else: 67 centerItem = cdApp.MenuBars(1).Menus(4).MenuItems(7) 68 if visible: 69 cdApp.Visible = 1 70 if theDoc and showDoc: 71 theDoc.Activate() 72 73 74def ReactivateChemDraw(openDoc=True, showDoc=True): 75 global cdApp, theDoc, theObjs 76 cdApp.Visible = 1 77 if openDoc: 78 theDoc = cdApp.Documents.Add() 79 if theDoc and showDoc: 80 theDoc.Activate() 81 theObjs = theDoc.Objects 82 83 84# ------------------------------------------------------------------ 85# interactions with Chemdraw 86# ------------------------------------------------------------------ 87def CDXConvert(inData, inFormat, outFormat): 88 """converts the data passed in from one format to another 89 90 inFormat should be one of the following: 91 chemical/x-cdx chemical/cdx 92 chemical/x-daylight-smiles chemical/daylight-smiles 93 chemical/x-mdl-isis chemical/mdl-isis 94 chemical/x-mdl-molfile chemical/mdl-molfile 95 chemical/x-mdl-rxn chemical/mdl-rxn 96 chemical/x-mdl-tgf chemical/mdl-tgf 97 chemical/x-questel-F1 98 chemical/x-questel-F1-query 99 100 outFormat should be one of the preceding or: 101 image/x-png image/png 102 image/x-wmf image/wmf 103 image/tiff 104 application/postscript 105 image/gif 106 """ 107 global theObjs, theDoc 108 if cdApp is None: 109 StartChemDraw() 110 if theObjs is None: 111 if theDoc is None: 112 theDoc = cdApp.Documents.Add() 113 theObjs = theDoc.Objects 114 theObjs.SetData(inFormat, inData, pythoncom.Missing) 115 outD = theObjs.GetData(outFormat) 116 theObjs.Clear() 117 return outD 118 119 120def CDXClean(inData, inFormat, outFormat): 121 """calls the CDXLib Clean function on the data passed in. 122 123 CDXLib_Clean attempts to clean (prettify) the data before 124 doing an output conversion. It can be thought of as CDXConvert++. 125 126 CDXClean supports the same input and output specifiers as CDXConvert 127 (see above) 128 129 """ 130 global cdApp, theDoc, theObjs, selectItem, cleanItem 131 if cdApp is None: 132 StartChemDraw() 133 if theObjs is None: 134 if theDoc is None: 135 theDoc = cdApp.Documents.Add() 136 theObjs = theDoc.Objects 137 theObjs.SetData(inFormat, inData, pythoncom.Missing) 138 theObjs.Select() 139 cleanItem.Execute() 140 outD = theObjs.GetData(outFormat) 141 theObjs.Clear() 142 return outD 143 144 145def CDXDisplay(inData, inFormat='chemical/cdx', clear=1): 146 """ displays the data in Chemdraw """ 147 global cdApp, theDoc, theObjs, selectItem, cleanItem, centerItem 148 if cdApp is None: 149 StartChemDraw() 150 try: 151 theDoc.Activate() 152 except Exception: 153 ReactivateChemDraw() 154 theObjs = theDoc.Objects 155 if clear: 156 theObjs.Clear() 157 theObjs.SetData(inFormat, inData, pythoncom.Missing) 158 return 159 160 161def CDXGrab(outFormat='chemical/x-mdl-molfile'): 162 """ returns the contents of the active chemdraw document 163 164 """ 165 global cdApp, theDoc 166 if cdApp is None: 167 res = "" 168 else: 169 cdApp.Visible = 1 170 if not cdApp.ActiveDocument: 171 ReactivateChemDraw() 172 try: 173 res = cdApp.ActiveDocument.Objects.GetData(outFormat) 174 except Exception: 175 res = "" 176 return res 177 178 179def CloseChemdraw(): 180 """ shuts down chemdraw 181 182 """ 183 global cdApp 184 try: 185 cdApp.Quit() 186 except Exception: 187 pass 188 Exit() 189 190 191def Exit(): 192 """ destroys our link to Chemdraw 193 194 """ 195 global cdApp 196 cdApp = None 197 198 199def SaveChemDrawDoc(fileName='save.cdx'): 200 """force chemdraw to save the active document 201 202 NOTE: the extension of the filename will determine the format 203 used to save the file. 204 """ 205 d = cdApp.ActiveDocument 206 d.SaveAs(fileName) 207 208 209def CloseChemDrawDoc(): 210 """force chemdraw to save the active document 211 212 NOTE: the extension of the filename will determine the format 213 used to save the file. 214 """ 215 d = cdApp.ActiveDocument 216 d.Close() 217 218 219def RaiseWindowNamed(nameRe): 220 # start by getting a list of all the windows: 221 cb = lambda x, y: y.append(x) 222 wins = [] 223 win32gui.EnumWindows(cb, wins) 224 225 # now check to see if any match our regexp: 226 tgtWin = -1 227 for win in wins: 228 txt = win32gui.GetWindowText(win) 229 if nameRe.match(txt): 230 tgtWin = win 231 break 232 233 if tgtWin >= 0: 234 win32gui.ShowWindow(tgtWin, 1) 235 win32gui.BringWindowToTop(tgtWin) 236 237 238def RaiseChemDraw(): 239 e = re.compile('^ChemDraw') 240 RaiseWindowNamed(e) 241 242 243try: 244 from PIL import Image 245 from io import StringIO 246 247 def SmilesToPilImage(smilesStr): 248 """takes a SMILES string and returns a PIL image using chemdraw 249 250 """ 251 return MolToPilImage(smilesStr, inFormat='chemical/daylight-smiles', outFormat='image/gif') 252 253 def MolToPilImage(dataStr, inFormat='chemical/daylight-smiles', outFormat='image/gif'): 254 """takes a molecule string and returns a PIL image using chemdraw 255 256 """ 257 # do the conversion... 258 res = CDXConvert(dataStr, inFormat, outFormat) 259 dataFile = StringIO(str(res)) 260 img = Image.open(dataFile).convert('RGB') 261 return img 262except ImportError: 263 264 def SmilesToPilImage(smilesStr): 265 print('You need to have PIL installed to use this functionality') 266 return None 267 268 def MolToPilImage(dataStr, inFormat='chemical/daylight-smiles', outFormat='image/gif'): 269 print('You need to have PIL installed to use this functionality') 270 return None 271 272# ------------------------------------------------------------------ 273# interactions with Chem3D 274# ------------------------------------------------------------------ 275c3dApp = None 276 277 278def StartChem3D(visible=0): 279 """ launches Chem3D """ 280 global c3dApp 281 c3dApp = Dispatch('Chem3D.Application') 282 if not c3dApp.Visible: 283 c3dApp.Visible = visible 284 285 286def CloseChem3D(): 287 """ shuts down Chem3D """ 288 global c3dApp 289 c3dApp.Quit() 290 c3dApp = None 291 292 293availChem3DProps = ('DipoleMoment', 'BendEnergy', 'Non14VDWEnergy', 'StericEnergy', 294 'StretchBendEnergy', 'StretchEnergy', 'TorsionEnergy', 'VDW14Energy') 295 296 297def Add3DCoordsToMol(data, format, props={}): 298 """ adds 3D coordinates to the data passed in using Chem3D 299 300 **Arguments** 301 302 - data: the molecular data 303 304 - format: the format of _data_. Should be something accepted by 305 _CDXConvert_ 306 307 - props: (optional) a dictionary used to return calculated properties 308 309 """ 310 global c3dApp 311 if c3dApp is None: 312 StartChem3D() 313 if format != 'chemical/mdl-molfile': 314 molData = CDXClean(data, format, 'chemical/mdl-molfile') 315 else: 316 molData = data 317 with tempfile.NamedTemporaryFile(suffix='.mol', delete=False) as molF: 318 molF.write(molData) 319 doc = c3dApp.Documents.Open(molF.name) 320 321 if not doc: 322 print('cannot open molecule') 323 raise ValueError('No Molecule') 324 325 # set up the MM2 job 326 job = Dispatch('Chem3D.MM2Job') 327 job.Type = 1 328 job.DisplayEveryIteration = 0 329 job.RecordEveryIteration = 0 330 331 # start the calculation... 332 doc.MM2Compute(job) 333 # and wait for it to finish 334 while doc.ComputeStatus in [0x434f4d50, 0x50454e44]: 335 pass 336 #outFName = tempfile.mktemp('.mol') 337 # this is horrible, but apparently Chem3D gets pissy with tempfiles: 338 outFName = os.getcwd() + '/to3d.mol' 339 doc.SaveAs(outFName) 340 341 # generate the properties 342 for prop in availChem3DProps: 343 props[prop] = eval('doc.%s' % prop) 344 345 doc.Close(0) 346 347 os.unlink(molF.name) 348 c3dData = open(outFName, 'r').read() 349 gone = 0 350 while not gone: 351 try: 352 os.unlink(outFName) 353 except Exception: 354 time.sleep(.5) 355 else: 356 gone = 1 357 return c3dData 358 359 360def OptimizeSDFile(inFileName, outFileName, problemFileName='problems.sdf', restartEvery=20): 361 """ optimizes the structure of every molecule in the input SD file 362 363 **Arguments** 364 365 - inFileName: name of the input SD file 366 367 - outFileName: name of the output SD file 368 369 - problemFileName: (optional) name of the SD file used to store molecules which 370 fail during the optimization process 371 372 - restartEvery: (optional) Chem3D will be shut down and restarted 373 every _restartEvery_ molecules to try and keep core leaks under control 374 375 """ 376 inFile = open(inFileName, 'r') 377 outFile = open(outFileName, 'w+') 378 problemFile = None 379 props = {} 380 lines = [] 381 nextLine = inFile.readline() 382 skip = 0 383 nDone = 0 384 t1 = time.time() 385 while nextLine != '': 386 if nextLine.find('M END') != -1: 387 lines.append(nextLine) 388 molBlock = ''.join(lines) 389 390 try: 391 newMolBlock = Add3DCoordsToMol(molBlock, 'chemical/mdl-molfile', props=props) 392 except Exception: 393 badBlock = molBlock 394 skip = 1 395 lines = [] 396 else: 397 skip = 0 398 lines = [newMolBlock] 399 elif nextLine.find('$$$$') != -1: 400 t2 = time.time() 401 nDone += 1 402 print('finished molecule %d in %f seconds' % (nDone, time.time() - t1)) 403 t1 = time.time() 404 if nDone % restartEvery == 0: 405 CloseChem3D() 406 StartChem3D() 407 outFile.close() 408 outFile = open(outFileName, 'a') 409 if not skip: 410 for prop in props.keys(): 411 lines.append('> <%s>\n%f\n\n' % (prop, props[prop])) 412 lines.append(nextLine) 413 outFile.write(''.join(lines)) 414 lines = [] 415 else: 416 skip = 0 417 lines.append(nextLine) 418 if problemFile is None: 419 problemFile = open(problemFileName, 'w+') 420 problemFile.write(badBlock) 421 problemFile.write(''.join(lines)) 422 lines = [] 423 else: 424 lines.append(nextLine) 425 nextLine = inFile.readline() 426 outFile.close() 427 if problemFile is not None: 428 problemFile.close() 429 430 431if __name__ == '__main__': 432 inStr = 'CCC(C=O)CCC' 433 img = SmilesToPilImage(inStr) 434 img.save('foo.jpg') 435 convStr = CDXClean(inStr, 'chemical/x-daylight-smiles', 'chemical/x-daylight-smiles') 436 print('in:', inStr) 437 print('out:', convStr) 438 convStr = CDXConvert(inStr, 'chemical/x-daylight-smiles', 'chemical/x-mdl-molfile') 439 print('in:', inStr) 440 print('out:', convStr) 441 convStr2 = CDXClean(convStr, 'chemical/x-mdl-molfile', 'chemical/x-mdl-molfile') 442 print('out2:', convStr2) 443 444 inStr = 'COc1ccc(c2onc(c2C(=O)NCCc3ccc(F)cc3)c4ccc(F)cc4)c(OC)c1' 445 convStr = CDXConvert(inStr, 'chemical/x-daylight-smiles', 'chemical/x-mdl-molfile') 446 out = open('test.mol', 'w+') 447 out.write(convStr) 448 out.close() 449