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