I’ve been working on a project in which I needed to generate logP values using ChemDraw 12, for thousands of molecules. Since I didn’t have access to the ChemScript module, I needed a way to automate this procedure. After fiddling around with Visual Studio and various debuggers, I came across the Windows Application Testing Using Python (WATSUP). This is a set of Python methods, built on top of the win32api package that allows one to interact with a Windows GUI programmatically. Thus one can identify a top level window, get a specific control (button, combo box etc) and click on buttons, menu items and so on.
After a little digging around (and liberal use of the dumpWindow() function in WATSUP), I was able to put together a simple bit of code that would load an SDF (containing a single structure) and save it as a CDXML file. For this to work, I make sure that the ChemDraw application is running and the “Chemical Properties” window is visible. On loading an SDF, the chemical properties (stuff like logP, MR, melting point etc) get computed automatically. We then “click” the paste button and then save to CDXML format. In the resultant CDXML file, the chemical property values are included – which can then be easily extracted using a regex. Here’s the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | import win32com.client import win32api import sys, os, time, datetime, glob, pprint from watsup.winGuiAuto import * from watsup.launcher import launchApp,terminateApp def getButtonControl(toplevel, buttonText): elems = dumpWindow(toplevel) openButtonHwnd = None for elem in elems: if len(elem) != 3: continue hwnd, name, klass = elem if name == buttonText and klass == 'Button': openButtonHwnd = hwnd break openButton = findControls(toplevel, wantedClass="Button", wantedText=buttonText) openButton = openButton[openButton.index(openButtonHwnd)] return openButton def setFileName(toplevel, filename): fnameCombo = findControl(toplevel, wantedClass='ComboBoxEx32') fnameComboEdit = findControl(fnameCombo, wantedClass = "Edit") setEditText(fnameComboEdit, filename) filename = "c:\\work\\test.sdf" form=findTopWindow(wantedText='ChemBioDraw Ultra') mainMenu = getTopMenu(form) # click the File->Open menu option activateMenuItem(form, ('file', 'open')) time.sleep(0.5) ## OK, we get the file selection window openWindow = findTopWindow(wantedText='Open') ## get the file type combo box and select the SDF format ftypeCombo = findControls(openWindow, wantedClass = "ComboBox") if len(getComboboxItems(ftypeCombo[0])) > 10: ftypeCombo = ftypeCombo[0] elif len(getComboboxItems(ftypeCombo[1])) > 10: ftypeCombo = ftypeCombo[1] else: ftypeCombo = ftypeCombo[2] selectComboboxItem(ftypeCombo, 14) ## get the filename combo box and set it setFileName(openWindow, filename) ## get the open button, click it and get the file openButton = getButtonControl(openWindow, "&Open") clickButton(openButton) time.sleep(1) ## get the properties window and then paste in the ## chemical properties that are autocalculated propWindow = findTopWindow(wantedText = "Chemical Properties", retryInterval = 0.1, maxWait = 5) pasteButton = findControl(propWindow, wantedClass="Button", wantedText="Paste") clickButton(pasteButton) ## now save the file as a cdxml newfilename = getOutputfileName(filename) activateMenuItem(form, ("file", "save as")) time.sleep(0.5) saveWindow = findTopWindow(wantedText='Save As') setFileName(saveWindow, newfilename) ## set file type ftypeCombo = findControls(saveWindow, wantedClass = "ComboBox") if len(getComboboxItems(ftypeCombo[0])) > 10: ftypeCombo = ftypeCombo[0] elif len(getComboboxItems(ftypeCombo[1])) > 10: ftypeCombo = ftypeCombo[1] else: ftypeCombo = ftypeCombo[2] selectComboboxItem(ftypeCombo, 1) ## get the save button and click it to save the file saveButton = getButtonControl(saveWindow, "&Save") clickButton(saveButton) time.sleep(1) ## looks like we have to do a save and then we close activateMenuItem(form, ['file', 'save']) activateMenuItem(form, ['file', 'close']) |
Obviously this approach is an inelegant hack and is dog slow – approximately 2.5 sec per structure. But in the absence of anything else, it gets the job done.
While implementing this solution there were a few quirks. For example, the widgets contained with in window, represent a hierarchy. The findControls() method traverses this hierarchy, but does not return the controls of the specific type in the same order on consecutive runs. So to find the appropriate combo box (say for the file type) I need to do some extra work (rather than just going with the first of three combo boxes that are located on a Open dialog). One contributing factor to the slowness is that I needed to insert a few sleep statements here and there, to ensure that the proper windows showed up before I started setting values in the various widgets. Finally, for some reason I had to do a “Save As” followed by a “Save” to get the final CDXML file with all the computed properties.