1 #!/bin/env python
2 #
3 # File: RDKitDrawMolecules.py
4 # Author: Manish Sud <msud@san.rr.com>
5 #
6 # Copyright (C) 2026 Manish Sud. All rights reserved.
7 #
8 # The functionality available in this script is implemented using RDKit, an
9 # open source toolkit for cheminformatics developed by Greg Landrum.
10 #
11 # This file is part of MayaChemTools.
12 #
13 # MayaChemTools is free software; you can redistribute it and/or modify it under
14 # the terms of the GNU Lesser General Public License as published by the Free
15 # Software Foundation; either version 3 of the License, or (at your option) any
16 # later version.
17 #
18 # MayaChemTools is distributed in the hope that it will be useful, but without
19 # any warranty; without even the implied warranty of merchantability of fitness
20 # for a particular purpose. See the GNU Lesser General Public License for more
21 # details.
22 #
23 # You should have received a copy of the GNU Lesser General Public License
24 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or
25 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330,
26 # Boston, MA, 02111-1307, USA.
27 #
28
29 from __future__ import print_function
30
31 import os
32 import sys
33 import time
34 import re
35
36 # RDKit imports...
37 try:
38 from rdkit import rdBase
39 from rdkit import Chem
40 from rdkit.Chem import AllChem
41 from rdkit.Chem import Draw
42 from rdkit.Chem.Draw.MolDrawing import DrawingOptions
43 except ImportError as ErrMsg:
44 sys.stderr.write("\nFailed to import RDKit module/package: %s\n" % ErrMsg)
45 sys.stderr.write("Check/update your RDKit environment and try again.\n\n")
46 sys.exit(1)
47
48 # MayaChemTools imports...
49 sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), "..", "lib", "Python"))
50 try:
51 from docopt import docopt
52 import MiscUtil
53 import RDKitUtil
54 except ImportError as ErrMsg:
55 sys.stderr.write("\nFailed to import MayaChemTools module/package: %s\n" % ErrMsg)
56 sys.stderr.write("Check/update your MayaChemTools environment and try again.\n\n")
57 sys.exit(1)
58
59 ScriptName = os.path.basename(sys.argv[0])
60 Options = {}
61 OptionsInfo = {}
62
63
64 def main():
65 """Start execution of the script."""
66
67 MiscUtil.PrintInfo(
68 "\n%s (RDKit v%s; MayaChemTools v%s; %s): Starting...\n"
69 % (ScriptName, rdBase.rdkitVersion, MiscUtil.GetMayaChemToolsVersion(), time.asctime())
70 )
71
72 (WallClockTime, ProcessorTime) = MiscUtil.GetWallClockAndProcessorTime()
73
74 # Retrieve command line arguments and options...
75 RetrieveOptions()
76
77 # Process and validate command line arguments and options...
78 ProcessOptions()
79
80 # Perform actions required by the script...
81 DrawMolecules()
82
83 MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName)
84 MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime))
85
86
87 def DrawMolecules():
88 """Draw molecules."""
89
90 Infile = OptionsInfo["Infile"]
91 Outfile = OptionsInfo["Outfile"]
92
93 # Read molecules...
94 MiscUtil.PrintInfo("\nReading file %s..." % Infile)
95
96 ValidMols, MolCount, ValidMolCount = RDKitUtil.ReadAndValidateMolecules(Infile, **OptionsInfo["InfileParams"])
97
98 MiscUtil.PrintInfo("Total number of molecules: %d" % MolCount)
99 MiscUtil.PrintInfo("Number of valid molecules: %d" % ValidMolCount)
100 MiscUtil.PrintInfo("Number of ignored molecules: %d" % (MolCount - ValidMolCount))
101
102 # Compute 2D coordinates...
103 if OptionsInfo["Compute2DCoords"]:
104 MiscUtil.PrintInfo("\nComputing 2D coordinates...")
105 for Mol in ValidMols:
106 AllChem.Compute2DCoords(Mol)
107
108 MiscUtil.PrintInfo("Generating image grid...")
109
110 # Setup atoms lists for highlighting atoms and bonds...
111 AtomLists = SetupAtomListsToHighlight(ValidMols)
112 BondLists = None
113
114 # Set up legends...
115 MolNames = None
116 if OptionsInfo["ShowMolName"]:
117 MolNames = []
118 MolCount = 0
119 for Mol in ValidMols:
120 MolCount += 1
121 MolName = RDKitUtil.GetMolName(Mol, MolCount)
122 MolNames.append(MolName)
123
124 # Perform alignment to a common template...
125 PerformAlignment(ValidMols)
126
127 # Generate appropriate output files...
128 if MiscUtil.CheckFileExt(Outfile, "svg"):
129 GenerateSVGImageFile(ValidMols, MolNames, AtomLists, BondLists)
130 elif MiscUtil.CheckFileExt(Outfile, "html htm"):
131 GenerateHTMLTableFile(ValidMols, MolNames, AtomLists, BondLists)
132 else:
133 GenerateImageFile(ValidMols, MolNames, AtomLists, BondLists)
134
135
136 def GenerateSVGImageFile(ValidMols, MolNames, AtomLists, BondLists):
137 """Generate a SVG image file."""
138
139 MolsSVGText = RDKitUtil.GetSVGForMolecules(
140 ValidMols,
141 OptionsInfo["NumOfMolsPerRow"],
142 OptionsInfo["MolImageWidth"],
143 OptionsInfo["MolImageHeight"],
144 Legends=MolNames,
145 AtomListsToHighlight=AtomLists,
146 BondListsToHighlight=BondLists,
147 BoldText=OptionsInfo["FontBold"],
148 )
149
150 MiscUtil.PrintInfo("\nGenerating SVG image file %s..." % OptionsInfo["Outfile"])
151
152 OutFH = open(OptionsInfo["Outfile"], "w")
153 OutFH.write(MolsSVGText)
154 OutFH.close()
155
156
157 def GenerateImageFile(ValidMols, MolNames, AtomLists, BondLists):
158 """Generate a non SVG image file."""
159
160 Outfile = OptionsInfo["Outfile"]
161
162 NumOfMolsPerRow = OptionsInfo["NumOfMolsPerRow"]
163 Width = OptionsInfo["MolImageWidth"]
164 Height = OptionsInfo["MolImageHeight"]
165
166 # Setup drawing options...
167 UpdatedDrawingOptions = DrawingOptions()
168 UpdatedDrawingOptions.atomLabelFontSize = int(OptionsInfo["AtomLabelFontSize"])
169 UpdatedDrawingOptions.bondLineWidth = float(OptionsInfo["BondLineWidth"])
170
171 try:
172 MolsImage = Draw.MolsToGridImage(
173 ValidMols,
174 molsPerRow=NumOfMolsPerRow,
175 subImgSize=(Width, Height),
176 legends=MolNames,
177 highlightAtomLists=AtomLists,
178 highlightBondLists=BondLists,
179 useSVG=False,
180 kekulize=OptionsInfo["Kekulize"],
181 options=UpdatedDrawingOptions,
182 )
183 except Exception:
184 # MolsToGridImage doesn't appear to handle the following parameters in the latest version of RDKit:
185 # . kekulize = OptionsInfo["Kekulize"], options = UpdatedDrawingOptions
186 MolsImage = Draw.MolsToGridImage(
187 ValidMols,
188 molsPerRow=NumOfMolsPerRow,
189 subImgSize=(Width, Height),
190 legends=MolNames,
191 highlightAtomLists=AtomLists,
192 highlightBondLists=BondLists,
193 useSVG=False,
194 returnPNG=False,
195 )
196
197 MiscUtil.PrintInfo("\nGenerating image file %s..." % Outfile)
198
199 if MiscUtil.CheckFileExt(Outfile, "pdf"):
200 if MolsImage.mode == "RGBA":
201 MolsImage = MolsImage.convert("RGB")
202
203 MolsImage.save(Outfile)
204
205
206 def GenerateHTMLTableFile(ValidMols, MolNames, HighlightAtomLists, HighlightBondLists):
207 """Generate a HTML table file."""
208
209 Outfile = OptionsInfo["Outfile"]
210
211 Writer = open(Outfile, "w")
212 if Writer is None:
213 MiscUtil.PrintError("Failed to setup a writer for output fie %s " % Outfile)
214
215 MiscUtil.PrintInfo("\nGenerating HTML table file %s..." % Outfile)
216
217 WriteHTMLPageHeader(Writer, len(ValidMols))
218 WriteHTMLPageTitle(Writer)
219
220 WriteHTMLTableHeader(Writer)
221 WriteHTMLTableRows(Writer, ValidMols, MolNames, HighlightAtomLists, HighlightBondLists)
222 WriteHTMLTableEnd(Writer)
223
224 WriteHTMLPageFooter(Writer)
225 WriteHTMLPageEnd(Writer)
226
227 if Writer is not None:
228 Writer.close()
229
230
231 def WriteHTMLTableRows(Writer, ValidMols, MolNames, HighlightAtomLists, HighlightBondLists):
232 """Write out HTML table rows."""
233
234 WriteTableHeaderRow(Writer, ValidMols)
235 WriteTableDataRows(Writer, ValidMols, MolNames, HighlightAtomLists, HighlightBondLists)
236 WriteTableFooterRow(Writer, ValidMols)
237
238
239 def WriteTableDataRows(Writer, ValidMols, MolNames, HighlightAtomLists, HighlightBondLists):
240 """Write out table data row."""
241
242 Writer.write(""" <tbody>\n""")
243
244 MolCount = len(ValidMols)
245 ColCount = GetColCount(MolCount)
246
247 for Index in range(0, MolCount, ColCount):
248 Writer.write(""" <tr>\n""")
249
250 if OptionsInfo["CounterCol"]:
251 Writer.write(""" <td></td>\n""")
252
253 for MolIndex in range(Index, (Index + ColCount)):
254 SetupStructureDataDrawing(Writer, MolIndex, ValidMols, MolNames, HighlightAtomLists, HighlightBondLists)
255
256 Writer.write(""" </tr>\n""")
257
258 Writer.write(""" </tbody>\n""")
259
260
261 def SetupStructureDataDrawing(Writer, MolIndex, Mols, MolNames, HighlightAtomLists, HighlightBondLists):
262 """Setup structure data drawing for a tabel cell."""
263
264 if MolIndex >= len(Mols):
265 Writer.write(""" <td></td>\n""")
266 return
267
268 Mol = Mols[MolIndex]
269 MolName = None if MolNames is None else MolNames[MolIndex]
270 HighlightAtomList = None if HighlightAtomLists is None else HighlightAtomLists[MolIndex]
271 HighlightBondList = None if HighlightBondLists is None else HighlightBondLists[MolIndex]
272
273 SVGText = RDKitUtil.GetInlineSVGForMolecule(
274 Mol,
275 OptionsInfo["MolImageWidth"],
276 OptionsInfo["MolImageHeight"],
277 Legend=MolName,
278 AtomListToHighlight=HighlightAtomList,
279 BondListToHighlight=HighlightBondList,
280 BoldText=OptionsInfo["FontBold"],
281 Base64Encoded=OptionsInfo["MolImageEncoded"],
282 )
283
284 PopoverTag = GetMolPopoverTag(Mol)
285 ImageTag = "img" if PopoverTag is None else "img %s" % PopoverTag
286
287 if OptionsInfo["MolImageEncoded"]:
288 SVGInlineImageTag = '%s src="data:image/svg+xml;base64,\n%s"' % (ImageTag, SVGText)
289 else:
290 SVGInlineImageTag = '%s src="data:image/svg+xml;charset=UTF-8,\n%s"' % (ImageTag, SVGText)
291
292 Writer.write(""" <td bgcolor="white"><%s></td>\n""" % SVGInlineImageTag)
293
294
295 def WriteTableHeaderRow(Writer, ValidMols):
296 """Write out table header row."""
297
298 if not OptionsInfo["TableHeader"]:
299 return
300
301 TableHeaderStyle = OptionsInfo["TableHeaderStyle"]
302 if TableHeaderStyle is None:
303 Writer.write(""" <thead>\n""")
304 Writer.write(""" <tr>\n""")
305 elif re.match("^(thead|table)", TableHeaderStyle):
306 Writer.write(""" <thead class="%s">\n""" % TableHeaderStyle)
307 Writer.write(""" <tr>\n""")
308 else:
309 Writer.write(""" <thead>\n""")
310 Writer.write(""" <tr bgcolor="%s"\n""" % TableHeaderStyle)
311
312 if OptionsInfo["CounterCol"]:
313 Writer.write(""" <th></th>\n""")
314
315 # Write out rest of the column headers...
316 MolCount = len(ValidMols)
317 ColCount = GetColCount(MolCount)
318 for ColIndex in range(0, ColCount):
319 ColLabel = MiscUtil.GetExcelStyleColumnLabel(ColIndex + 1)
320 Writer.write(""" <th>%s</th>\n""" % ColLabel)
321
322 Writer.write(""" </tr>\n""")
323 Writer.write(""" </thead>\n""")
324
325
326 def WriteTableFooterRow(Writer, ValidMols):
327 """Write out table footer row."""
328
329 if not OptionsInfo["TableFooter"]:
330 return
331
332 Writer.write(""" <tfoot>\n""")
333 Writer.write(""" <tr>\n""")
334
335 if OptionsInfo["CounterCol"]:
336 Writer.write(""" <td></td>\n""")
337
338 # Write out rest of the column headers...
339 MolCount = len(ValidMols)
340 ColCount = GetColCount(MolCount)
341 for ColIndex in range(0, ColCount):
342 ColLabel = MiscUtil.GetExcelStyleColumnLabel(ColIndex + 1)
343 Writer.write(""" <td>%s</td>\n""" % ColLabel)
344
345 Writer.write(""" </tr>\n""")
346 Writer.write(""" </tfoot>\n""")
347
348
349 def WriteHTMLPageHeader(Writer, MolCount):
350 """Write out HTML page header."""
351
352 ColCount = GetColCount(MolCount)
353
354 # Exclude counter and structure columns from sorting and searching...
355 if OptionsInfo["CounterCol"]:
356 ColIndicesList = ["0"]
357 ColVisibilityExcludeColIndicesList = ["0"]
358 ColIndexOffset = 1
359 FreezeLeftColumns = "1"
360 else:
361 ColIndicesList = []
362 ColVisibilityExcludeColIndicesList = []
363 ColIndexOffset = 0
364
365 MaxDataColVisColCount = 25
366 for Index in range(0, ColCount):
367 ColIndex = Index + ColIndexOffset
368 ColIndicesList.append("%s" % ColIndex)
369
370 if OptionsInfo["ColVisibility"]:
371 if Index >= MaxDataColVisColCount:
372 ColVisibilityExcludeColIndicesList.append("%s" % ColIndex)
373
374 ColIndices = MiscUtil.JoinWords(ColIndicesList, ", ") if len(ColIndicesList) else ""
375 ColVisibilityExcludeColIndices = (
376 MiscUtil.JoinWords(ColVisibilityExcludeColIndicesList, ", ") if len(ColVisibilityExcludeColIndicesList) else ""
377 )
378
379 DataColVisibilityExclude = False
380 if OptionsInfo["ColVisibility"]:
381 if ColCount > MaxDataColVisColCount:
382 DataColVisibilityExclude = True
383 MiscUtil.PrintWarning(
384 "The number of data columns, %d, is quite large. Only first %d data columns will be available in column visibility pulldown."
385 % (ColCount, MaxDataColVisColCount)
386 )
387
388 DisplayButtons = False
389 if OptionsInfo["ColVisibility"]:
390 if ColCount > 0:
391 DisplayButtons = True
392
393 FreezeCols = False
394 if OptionsInfo["CounterCol"] and OptionsInfo["ScrollX"]:
395 FreezeCols = True
396
397 Paging = "true" if OptionsInfo["Paging"] else "false"
398 PageLength = "%d" % OptionsInfo["PageLength"]
399 PagingType = '"%s"' % OptionsInfo["PagingType"]
400
401 ScrollX = "true" if OptionsInfo["ScrollX"] else "false"
402
403 ScrollY = ""
404 if OptionsInfo["ScrollY"]:
405 if re.search("vh$", OptionsInfo["ScrollYSize"]):
406 ScrollY = '"%s"' % OptionsInfo["ScrollYSize"]
407 else:
408 ScrollY = "%s" % OptionsInfo["ScrollYSize"]
409
410 # Start HTML header...
411 Title = "Molecules table" if OptionsInfo["Header"] is None else OptionsInfo["Header"]
412
413 Writer.write(
414 """\
415 <!doctype html>
416 <html lang="en">
417 <head>
418 <title>%s</title>
419 <meta charset="utf-8">
420 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
421 <link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
422 <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.16/css/dataTables.bootstrap4.min.css">
423
424 """
425 % (Title)
426 )
427
428 if FreezeCols:
429 Writer.write("""\
430 <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/fixedcolumns/3.2.4/css/fixedColumns.bootstrap4.min.css">
431 """)
432
433 if OptionsInfo["KeysNavigation"]:
434 Writer.write("""\
435 <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/keytable/2.3.2/css/keyTable.bootstrap4.min.css">
436 """)
437
438 Writer.write("""\
439
440 <script type="text/javascript" language="javascript" src="https://code.jquery.com/jquery-1.12.4.js"></script>
441 <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js"></script>
442 <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/1.10.16/js/dataTables.bootstrap4.min.js"></script>
443
444 """)
445
446 if OptionsInfo["Popover"]:
447 Writer.write("""\
448 <script type="text/javascript" language="javascript" src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
449 <script type="text/javascript" language="javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
450
451 """)
452
453 if DisplayButtons:
454 Writer.write("""\
455 <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/buttons/1.5.1/js/dataTables.buttons.min.js"></script>
456 <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.bootstrap4.min.js"></script>
457 <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.colVis.min.js"></script>
458
459 """)
460
461 if FreezeCols:
462 Writer.write("""\
463 <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/fixedcolumns/3.2.4/js/dataTables.fixedColumns.min.js"></script>
464 """)
465
466 if OptionsInfo["KeysNavigation"]:
467 Writer.write("""\
468 <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/keytable/2.3.2/js/dataTables.keyTable.min.js"></script>
469 """)
470
471 # Intialize table using Bootstrap, DataTables and JQuery frameworks...
472 Writer.write("""\
473
474 <script type="text/javascript" class="init">
475
476 $(document).ready(function() {
477 """)
478
479 if OptionsInfo["Popover"]:
480 Writer.write("""\
481 $('.MolPopover').popover();
482
483 """)
484
485 Writer.write(
486 """\
487 var MolsTable = $('#MolsTable').DataTable( {
488 "columnDefs": [
489 {
490 "orderable": false,
491 "searchable": false,
492 "targets": [%s]
493 },
494 """
495 % (ColIndices)
496 )
497
498 if OptionsInfo["ColVisibility"]:
499 Writer.write(
500 """\
501 {
502 "className": "noColVisCtrl",
503 "targets": [%s]
504 }
505 """
506 % (ColVisibilityExcludeColIndices)
507 )
508
509 Writer.write("""\
510 ],
511 """)
512
513 # Set up dom for displaying button and other options...
514 if OptionsInfo["ColVisibility"]:
515 if OptionsInfo["Paging"]:
516 Writer.write("""\
517 "dom": "<'row'<'col-sm-6'l><'col-sm-6'<'float-right'B>>>" +
518 "<'row'<'col-sm-12'tr>>" +
519 "<'row'<'col-sm-5'i><'col-sm-7'p>>",
520 """)
521 else:
522 Writer.write("""\
523 "dom": "<'row'<'col'<'float-right'B>>>" +
524 "<'row'<'col-sm-12'tr>>" +
525 "<'row'<'col-sm-5'i><'col-sm-7'p>>",
526 """)
527 else:
528 Writer.write("""\
529 "dom": "<'row'<'col'l>>" +
530 "<'row'<'col-sm-12'tr>>" +
531 "<'row'<'col-sm-5'i><'col-sm-7'p>>",
532 """)
533
534 #
535 if OptionsInfo["ColVisibility"]:
536 # Set up buttons...
537 Writer.write("""\
538 "buttons": [
539 {
540 "extend": "colvis",
541 "text": "Column visibility",
542 "className": "btn btn-outline-light text-dark",
543 "columns": ":not(.noColVisCtrl)",
544 """)
545 if not DataColVisibilityExclude:
546 Writer.write("""\
547 "prefixButtons": [ "colvisRestore" ],
548 """)
549
550 Writer.write("""\
551 "columnText": function ( dt, colIndex, colLabel ) {
552 return "Column " + (colIndex + 1);
553 },
554 }
555 ],
556 """)
557
558 # Write out rest of the variables for DataTables...
559 if FreezeCols:
560 Writer.write(
561 """\
562 "fixedColumns": {
563 "leftColumns": %s
564 },
565 """
566 % (FreezeLeftColumns)
567 )
568
569 if OptionsInfo["KeysNavigation"]:
570 Writer.write("""\
571 "keys": true,
572 """)
573
574 Writer.write(
575 """\
576 "pageLength": %s,
577 "lengthMenu": [ [5, 10, 15, 25, 50, 100, 500, 1000, -1], [5, 10, 15, 25, 50, 100, 500, 1000, "All"] ],
578 "paging": %s,
579 "pagingType": %s,
580 "scrollX": %s,
581 "scrollY": %s,
582 "scrollCollapse": true,
583 "order": [],
584 } );
585 """
586 % (PageLength, Paging, PagingType, ScrollX, ScrollY)
587 )
588
589 if OptionsInfo["CounterCol"]:
590 Writer.write("""\
591 MolsTable.on( 'order.dt search.dt', function () {
592 MolsTable.column(0, {search:'applied', order:'applied'}).nodes().each( function (cell, rowIndex) {
593 cell.innerHTML = rowIndex + 1;
594 } );
595 } ).draw();
596 """)
597
598 # End of Javacscript code...
599 Writer.write("""\
600 } );
601
602 </script>
603 """)
604
605 # Finish up HTML header...
606 Writer.write("""\
607
608 </head>
609 <body>
610 <div class="container-fluid">
611 <br/>
612 """)
613
614
615 def WriteHTMLPageEnd(Writer):
616 """Write out HTML page end."""
617
618 Writer.write("""\
619 </div>
620 </body>
621 </html>
622 """)
623
624
625 def WriteHTMLPageTitle(Writer):
626 """Write out HTML page title."""
627
628 if OptionsInfo["Header"] is None:
629 return
630
631 Writer.write(
632 """ <%s class="text-center">%s</%s>\n"""
633 % (OptionsInfo["HeaderStyle"], OptionsInfo["Header"], OptionsInfo["HeaderStyle"])
634 )
635
636
637 def WriteHTMLPageFooter(Writer):
638 """Write out HTML page footer."""
639
640 if OptionsInfo["Footer"] is None:
641 return
642
643 Writer.write(""" <br/>\n <p class="%s">%s</p>\n""" % (OptionsInfo["FooterClass"], OptionsInfo["Footer"]))
644
645
646 def WriteHTMLTableHeader(Writer):
647 """Write out HTML table header."""
648
649 if OptionsInfo["TableStyle"] is None:
650 Writer.write("""\n <table id="MolsTable" cellspacing="0" width="100%">\n""")
651 else:
652 Writer.write(
653 """ <table id="MolsTable" class="%s" cellspacing="0" width="100%s">\n"""
654 % (OptionsInfo["TableStyle"], "%")
655 )
656
657
658 def WriteHTMLTableEnd(Writer):
659 """Write out HTML table end."""
660
661 Writer.write(""" </table>\n\n""")
662
663
664 def GetColCount(MolCount):
665 """Get tabke column count."""
666
667 ColCount = OptionsInfo["NumOfMolsPerRow"] if OptionsInfo["NumOfMolsPerRow"] <= MolCount else MolCount
668
669 return ColCount
670
671
672 def SetupAtomListsToHighlight(ValidMols):
673 """Set up atom lists to highlight using specified SMARTS pattern."""
674
675 AtomListsToHighlight = None
676 if OptionsInfo["HighlightSMARTSPattern"] is None:
677 return AtomListsToHighlight
678
679 PatternMol = Chem.MolFromSmarts(OptionsInfo["HighlightSMARTSPattern"])
680 AtomListsToHighlight = []
681 for ValidMol in ValidMols:
682 # Get matched atom lists and flatten it...
683 MatchedAtomsLists = ValidMol.GetSubstructMatches(PatternMol)
684 MatchedAtoms = [Atom for AtomsList in MatchedAtomsLists for Atom in AtomsList]
685 AtomListsToHighlight.append(MatchedAtoms)
686
687 return AtomListsToHighlight
688
689
690 def PerformAlignment(ValidMols):
691 """Perform alignment to a common template specified by a SMARTS pattern."""
692
693 if OptionsInfo["AlignmentSMARTSPattern"] is None:
694 return
695
696 PatternMol = Chem.MolFromSmarts(OptionsInfo["AlignmentSMARTSPattern"])
697 AllChem.Compute2DCoords(PatternMol)
698
699 MatchedValidMols = [ValidMol for ValidMol in ValidMols if ValidMol.HasSubstructMatch(PatternMol)]
700 for ValidMol in MatchedValidMols:
701 AllChem.GenerateDepictionMatching2DStructure(ValidMol, PatternMol)
702
703
704 def GetMolPopoverTag(Mol):
705 """Set up a popover window containing any additional information about molecule."""
706
707 if not OptionsInfo["Popover"]:
708 return None
709
710 # Set up data label and values...
711 AvailableDataLabels = Mol.GetPropNames(includePrivate=False, includeComputed=False)
712
713 DataContentLines = []
714 MaxDataCharWidth = OptionsInfo["PopoverTextWidth"]
715 MaxDataDisplayCount = OptionsInfo["PopoverDataCount"]
716
717 DataDisplayCount = 0
718 SkippedDataDisplay = False
719 for DataLabel in AvailableDataLabels:
720 DataDisplayCount += 1
721 if DataDisplayCount > MaxDataDisplayCount:
722 SkippedDataDisplay = True
723 break
724
725 DataValue = "%s" % Mol.GetProp(DataLabel)
726 DataValue = DataValue.strip()
727 if MiscUtil.IsEmpty(DataValue):
728 continue
729
730 # Change any new lines to ;
731 if re.search("(\r\n|\r|\n)", DataValue):
732 DataValue = re.sub("(\r\n|\r|\n)", "; ", DataValue)
733
734 DataValue = MiscUtil.TruncateText(DataValue, MaxDataCharWidth, "...")
735 DataValue = MiscUtil.ReplaceHTMLEntitiesInText(DataValue)
736
737 DataContent = "<b>%s</b>: %s" % (DataLabel, DataValue)
738 DataContentLines.append(DataContent)
739
740 if not len(DataContentLines):
741 return None
742
743 if SkippedDataDisplay:
744 DataContent = "<b>... ... ...</b>"
745 DataContentLines.append(DataContent)
746
747 DataContent = "Showing 1 to %s of %s" % (MaxDataDisplayCount, len(AvailableDataLabels))
748 DataContentLines.append(DataContent)
749 else:
750 DataContent = "Showing 1 to %s of %s" % (DataDisplayCount, len(AvailableDataLabels))
751 DataContentLines.append(DataContent)
752
753 DataContent = MiscUtil.JoinWords(DataContentLines, "<br/>")
754 PopoverTag = (
755 """class="MolPopover" data-toggle="popover" data-html="true" data-trigger="click" data-placement="right" title="<span class='small'><b>Additional Information</b></span>" data-content="<span class='small'>%s</span>" """
756 % DataContent
757 )
758
759 return PopoverTag
760
761
762 def ProcessOptions():
763 """Process and validate command line arguments and options."""
764
765 MiscUtil.PrintInfo("Processing options...")
766
767 # Validate options...
768 ValidateOptions()
769
770 OptionsInfo["Infile"] = Options["--infile"]
771 OptionsInfo["Outfile"] = Options["--outfile"]
772 OptionsInfo["Overwrite"] = Options["--overwrite"]
773
774 # No need for any RDKit specific --outfileParams....
775 OptionsInfo["InfileParams"] = MiscUtil.ProcessOptionInfileParameters(
776 "--infileParams", Options["--infileParams"], OptionsInfo["Infile"]
777 )
778
779 AlignmentSMARTSPattern = None
780 if not re.match("^None$", Options["--alignmentSMARTS"], re.I):
781 AlignmentSMARTSPattern = Options["--alignmentSMARTS"]
782 OptionsInfo["AlignmentSMARTSPattern"] = AlignmentSMARTSPattern
783
784 OptionsInfo["AtomLabelFontSize"] = Options["--atomLabelFontSize"]
785 OptionsInfo["BondLineWidth"] = Options["--bondLineWidth"]
786
787 Compute2DCoords = True
788 if re.match("^yes$", Options["--compute2DCoords"], re.I):
789 Compute2DCoords = True
790 elif re.match("^no$", Options["--compute2DCoords"], re.I):
791 Compute2DCoords = False
792 OptionsInfo["Compute2DCoords"] = Compute2DCoords
793
794 CounterCol = True
795 if re.match("^no$", Options["--counterCol"], re.I):
796 CounterCol = False
797 OptionsInfo["CounterCol"] = CounterCol
798
799 ColVisibility = True
800 if re.match("^no$", Options["--colVisibility"], re.I):
801 ColVisibility = False
802 OptionsInfo["ColVisibility"] = ColVisibility
803
804 OptionsInfo["FontBold"] = True
805 if re.match("^no$", Options["--fontBold"], re.I):
806 OptionsInfo["FontBold"] = False
807
808 Footer = None
809 if not re.match("^None$", Options["--footer"], re.I):
810 Footer = Options["--footer"]
811 OptionsInfo["Footer"] = Footer
812
813 FooterClass = Options["--footerClass"].strip()
814 if MiscUtil.IsEmpty(FooterClass):
815 MiscUtil.PrintError('The value specified using option "--footerClass" is empty.')
816 OptionsInfo["FooterClass"] = FooterClass
817
818 Header = None
819 if not re.match("^None$", Options["--header"], re.I):
820 Header = Options["--header"]
821 OptionsInfo["Header"] = Header
822
823 HeaderStyle = Options["--headerStyle"].strip()
824 if MiscUtil.IsEmpty(HeaderStyle):
825 MiscUtil.PrintError('The value specified using option "--headerStyle" is empty.')
826 OptionsInfo["HeaderStyle"] = HeaderStyle
827
828 HighlightSMARTSPattern = None
829 if not re.match("^None$", Options["--highlightSMARTS"], re.I):
830 HighlightSMARTSPattern = Options["--highlightSMARTS"]
831 OptionsInfo["HighlightSMARTSPattern"] = HighlightSMARTSPattern
832
833 OptionsInfo["Kekulize"] = True
834 if re.match("^no$", Options["--kekulize"], re.I):
835 OptionsInfo["Kekulize"] = False
836
837 OptionsInfo["KeysNavigation"] = True
838 if re.match("^no$", Options["--keysNavigation"], re.I):
839 OptionsInfo["KeysNavigation"] = False
840
841 SizeValues = Options["--molImageSize"].split(",")
842 OptionsInfo["MolImageWidth"] = int(SizeValues[0])
843 OptionsInfo["MolImageHeight"] = int(SizeValues[1])
844
845 OptionsInfo["MolImageEncoded"] = True
846 if re.match("^no$", Options["--molImageEncoded"], re.I):
847 OptionsInfo["MolImageEncoded"] = False
848
849 OptionsInfo["NumOfMolsPerRow"] = int(Options["--numOfMolsPerRow"])
850
851 OptionsInfo["Paging"] = True
852 if re.match("^no$", Options["--paging"], re.I):
853 OptionsInfo["Paging"] = False
854
855 PagingType = Options["--pagingType"]
856 if not re.match("^(numbers|simple|simple_numbers|full|full_numbers|simple_number)$", Options["--pagingType"], re.I):
857 MiscUtil.PrintWarning(
858 'The paging type name, %s, specified using option "--pagingType" appears to be a unknown type...'
859 % (PagingType)
860 )
861 OptionsInfo["PagingType"] = PagingType.lower()
862
863 OptionsInfo["PageLength"] = int(Options["--pageLength"])
864
865 OptionsInfo["Popover"] = True
866 if re.match("^no$", Options["--popover"], re.I):
867 OptionsInfo["Popover"] = False
868 OptionsInfo["PopoverDataCount"] = int(Options["--popoverDataCount"])
869 OptionsInfo["PopoverTextWidth"] = int(Options["--popoverTextWidth"])
870
871 OptionsInfo["ShowMolName"] = True
872 if re.match("^no$", Options["--showMolName"], re.I):
873 OptionsInfo["ShowMolName"] = False
874
875 OptionsInfo["ScrollX"] = True
876 if re.match("^no$", Options["--scrollX"], re.I):
877 OptionsInfo["ScrollX"] = False
878
879 OptionsInfo["ScrollY"] = True
880 if re.match("^no$", Options["--scrollY"], re.I):
881 OptionsInfo["ScrollY"] = False
882
883 OptionsInfo["ScrollYSize"] = Options["--scrollYSize"]
884 if re.match("vh$", Options["--scrollYSize"], re.I):
885 ScrollYSize = int(re.sub("vh$", "", Options["--scrollYSize"]))
886 if ScrollYSize <= 0:
887 MiscUtil.PrintError(
888 'The value specified, %s, for option "--scrollYSize" is not valid. Supported value: > 0 followed by "vh"'
889 % Options["--scrollYSize"]
890 )
891
892 TableStyle = None
893 if not re.match("^None$", Options["--tableStyle"], re.I):
894 if re.match("^All$", Options["--tableStyle"], re.I):
895 TableStyle = "table table-striped table-bordered table-hover table-dark"
896 else:
897 TableStyle = re.sub(" ", "", Options["--tableStyle"])
898 for Style in [Style for Style in TableStyle.split(",")]:
899 if not re.match("^(table|table-striped|table-bordered|table-hover|table-dark|table-sm)$", Style, re.I):
900 MiscUtil.PrintWarning(
901 'The table style name, %s, specified using option "-t, --tableStyle" appears to be a unknown style...'
902 % (Style)
903 )
904 TableStyle = re.sub(",", " ", TableStyle.lower())
905 OptionsInfo["TableStyle"] = TableStyle
906
907 OptionsInfo["TableFooter"] = True
908 if re.match("^no$", Options["--tableFooter"], re.I):
909 OptionsInfo["TableFooter"] = False
910
911 OptionsInfo["TableHeader"] = True
912 if re.match("^no$", Options["--tableHeader"], re.I):
913 OptionsInfo["TableHeader"] = False
914
915 TableHeaderStyle = None
916 if not re.match("^None$", Options["--tableHeaderStyle"], re.I):
917 TableHeaderStyle = Options["--tableHeaderStyle"]
918 TableHeaderStyle = TableHeaderStyle.lower()
919 CheckOptionTableClassColorValues("--tableHeaderStyle", [TableHeaderStyle])
920 OptionsInfo["TableHeaderStyle"] = TableHeaderStyle
921
922
923 def CheckOptionTableClassColorValues(OptionName, ColorsList):
924 """Check names of table color classes and issue a warning for unknown names."""
925
926 TableClassColors = [
927 "thead-dark",
928 "thead-light",
929 "table-primary",
930 "table-success",
931 "table-danger",
932 "table-info",
933 "table-warning",
934 "table-active",
935 "table-secondary",
936 "table-light",
937 "table-dark",
938 "bg-primary",
939 "bg-success",
940 "bg-danger",
941 "bg-info",
942 "bg-warning",
943 "bg-secondary",
944 "bg-dark",
945 "bg-light",
946 ]
947
948 for Color in ColorsList:
949 if Color not in TableClassColors:
950 MiscUtil.PrintWarning(
951 'The color class name, %s, specified using option "%s" appears to be a unknown name...'
952 % (Color, OptionName)
953 )
954
955
956 def RetrieveOptions():
957 """Retrieve command line arguments and options."""
958
959 # Get options...
960 global Options
961 Options = docopt(_docoptUsage_)
962
963 # Set current working directory to the specified directory...
964 WorkingDir = Options["--workingdir"]
965 if WorkingDir:
966 os.chdir(WorkingDir)
967
968 # Handle examples option...
969 if "--examples" in Options and Options["--examples"]:
970 MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_))
971 sys.exit(0)
972
973
974 def ValidateOptions():
975 """Validate option values."""
976
977 MiscUtil.ValidateOptionFilePath("-i, --infile", Options["--infile"])
978 MiscUtil.ValidateOptionFileExt("-i, --infile", Options["--infile"], "sdf sd mol smi csv tsv txt")
979
980 MiscUtil.ValidateOptionsOutputFileOverwrite(
981 "-o, --outfile", Options["--outfile"], "--overwrite", Options["--overwrite"]
982 )
983 MiscUtil.ValidateOptionsDistinctFileNames(
984 "-i, --infile", Options["--infile"], "-o, --outfile", Options["--outfile"]
985 )
986
987 if not re.match("^None$", Options["--alignmentSMARTS"], re.I):
988 PatternMol = Chem.MolFromSmarts(Options["--alignmentSMARTS"])
989 if PatternMol is None:
990 MiscUtil.PrintError(
991 'The value specified, %s, using option "--alignmentSMARTS" is not a valid SMARTS: Failed to create pattern molecule'
992 % Options["--alignmentSMARTS"]
993 )
994
995 MiscUtil.ValidateOptionIntegerValue("--atomLabelFontSize", Options["--atomLabelFontSize"], {">": 0})
996 MiscUtil.ValidateOptionFloatValue("-b, --bondLineWidth", Options["--bondLineWidth"], {">": 0.0})
997
998 MiscUtil.ValidateOptionTextValue("--compute2DCoords", Options["--compute2DCoords"], "yes no auto")
999
1000 MiscUtil.ValidateOptionTextValue("--counterCol", Options["--counterCol"], "yes no")
1001 MiscUtil.ValidateOptionTextValue("--colVisibility", Options["--colVisibility"], "yes no")
1002
1003 MiscUtil.ValidateOptionTextValue("--f, -fontBold", Options["--fontBold"], "yes no")
1004
1005 if not re.match("^None$", Options["--highlightSMARTS"], re.I):
1006 PatternMol = Chem.MolFromSmarts(Options["--highlightSMARTS"])
1007 if PatternMol is None:
1008 MiscUtil.PrintError(
1009 'The value specified, %s, using option "--highlightSMARTS" is not a valid SMARTS: Failed to create pattern molecule'
1010 % Options["--highlightSMARTS"]
1011 )
1012
1013 MiscUtil.ValidateOptionTextValue("--kekulize", Options["--kekulize"], "yes no")
1014
1015 MiscUtil.ValidateOptionTextValue("-k, --keysNavigation", Options["--keysNavigation"], "yes no")
1016
1017 MiscUtil.ValidateOptionNumberValues("-m, --molImageSize", Options["--molImageSize"], 2, ",", "integer", {">": 0})
1018 MiscUtil.ValidateOptionTextValue("--molImageEncoded", Options["--molImageEncoded"], "yes no")
1019
1020 MiscUtil.ValidateOptionIntegerValue("--numOfMolsPerRow", Options["--numOfMolsPerRow"], {">": 0})
1021
1022 MiscUtil.ValidateOptionTextValue("-p, --paging", Options["--paging"], "yes no")
1023 MiscUtil.ValidateOptionIntegerValue("--pageLength", Options["--pageLength"], {">": 0})
1024
1025 MiscUtil.ValidateOptionTextValue("--popover", Options["--popover"], "yes no")
1026 MiscUtil.ValidateOptionIntegerValue("--popoverDataCount", Options["--popoverDataCount"], {">": 0})
1027 MiscUtil.ValidateOptionIntegerValue("--popoverTextWidth", Options["--popoverTextWidth"], {">": 0})
1028
1029 MiscUtil.ValidateOptionTextValue("--showMolName", Options["--showMolName"], "yes no")
1030
1031 MiscUtil.ValidateOptionTextValue("--scrollX", Options["--scrollX"], "yes no")
1032 MiscUtil.ValidateOptionTextValue("--scrollY", Options["--scrollY"], "yes no")
1033 if not re.search("vh$", Options["--scrollYSize"], re.I):
1034 MiscUtil.ValidateOptionIntegerValue("--scrollYSize", Options["--scrollYSize"], {">": 0})
1035
1036 MiscUtil.ValidateOptionTextValue("--tableFooter", Options["--tableFooter"], "yes no")
1037 MiscUtil.ValidateOptionTextValue("--tableHeader", Options["--tableHeader"], "yes no")
1038
1039
1040 # Setup a usage string for docopt...
1041 _docoptUsage_ = """
1042 RDKitDrawMolecules.py - Draw molecules and generate an image or HTML file
1043
1044 Usage:
1045 RDKitDrawMolecules.py [--alignmentSMARTS <SMARTS>] [--atomLabelFontSize <number>]
1046 [--bondLineWidth <number>] [--compute2DCoords <yes | no>] [--counterCol <yes or no>]
1047 [--colVisibility <yes or no>] [--fontBold <yes or no>] [--footer <text>] [--footerClass <text>]
1048 [--header <text>] [--headerStyle <text>] [--highlightSMARTS <SMARTS>]
1049 [--infileParams <Name,Value,...>] [--kekulize <yes or no>] [--keysNavigation <yes or no>]
1050 [--molImageSize <width,height>] [--molImageEncoded <yes or no> ]
1051 [--numOfMolsPerRow <number>] [--overwrite] [--paging <yes or no>]
1052 [--pagingType <numbers, simple, ...>] [--pageLength <number>]
1053 [--popover <yes or no>] [--popoverDataCount <number>] [--popoverTextWidth <number>]
1054 [--showMolName <yes or no>] [--scrollX <yes or no>] [--scrollY <yes or no>]
1055 [--scrollYSize <number>] [--tableFooter <yes or no>] [--tableHeader <yes or no>]
1056 [--tableHeaderStyle <thead-dark,thead-light,...>]
1057 [--tableStyle <table,table-striped,...>] [-w <dir>] -i <infile> -o <outfile>
1058 RDKitDrawMolecules.py -h | --help | -e | --examples
1059
1060 Description:
1061 Draw molecules in a grid and write them out as an image file or a HTML table file. The
1062 SVG image or HTML table file appears to be the best among all the available image file
1063 options, as rendered in a browser. The Python modules aggdraw/cairo are required to
1064 generate high quality PNG images.
1065
1066 The drawing of the molecules are embedded in HTML table columns as in line SVG
1067 images. The HTML table is an interactive table and requires internet access for viewing
1068 in a browser. It employs he following frameworks: JQuery, Bootstrap, and DataTable.
1069
1070 The options '--atomLabelFontSize' and '--bondLineWidth' don't appear to work
1071 during the generation of a SVG image. In addition, these may not work for other
1072 image types in the latest version of RDKIT.
1073
1074 The supported input file formats are: Mol (.mol), SD (.sdf, .sd), SMILES (.smi,
1075 .txt, .csv, .tsv)
1076
1077 The output image file can be saved in any format supported by the Python Image
1078 Library (PIL). The image format is automatically detected from the output file extension.
1079
1080 Some of the most common output image file formats are: GIF (.gif), JPEG (.jpg),
1081 PNG (.png), SVG (.svg), TIFF (.tif). In addition, a HTML (.html) file format
1082 containing a table is supported.
1083
1084 Options:
1085 -a, --alignmentSMARTS <SMARTS> [default: none]
1086 SMARTS pattern for aligning molecules to a common template.
1087 --atomLabelFontSize <number> [default: 12]
1088 Font size for drawing atom labels. This option is ignored during the generation of
1089 a SVG and HTML output file. This option may not work in the latest version of RDKit.
1090 -b, --bondLineWidth <number> [default: 1.2]
1091 Line width for drawing bonds. This option is ignored during the generation of a SVG
1092 and HTML output file. This option may not work in the latest version of RDKit.
1093 -c, --compute2DCoords <yes or no> [default: auto]
1094 Compute 2D coordinates of molecules before drawing. Default: yes for all file
1095 formats.
1096 --counterCol <yes or no> [default: yes]
1097 Show a counter column as the first column in the table. It contains the position
1098 for each row in the HTML table. This option is only used during the generation of
1099 a HTML table file.
1100 --colVisibility <yes or no> [default: yes]
1101 Show a dropdown button to toggle visibility of columns in the table. This option is
1102 only used during the generation of a HTML table file.
1103 -e, --examples
1104 Print examples.
1105 -f --fontBold <yes or no> [default: yes]
1106 Make all text fonts bold during the generation of a SVG and HTML output file. This
1107 option is ignored for all other output files. This option may not work in the latest
1108 version of RDKit.
1109 --footer <text> [default: none]
1110 Footer text to insert at the bottom of the HTML page after the table. This option is
1111 only used during the generation of a HTML table file.
1112 --footerClass <text> [default: small text-center text-muted]
1113 Footer class style to use with <p> tag. This option is only used during the
1114 generation of a HTML table file.
1115 -h, --help
1116 Print this help message.
1117 --header <text> [default: none]
1118 Header text to insert at the top of the HTML page before the table. This option is
1119 only used during the generation of a HTML table file.
1120 --headerStyle <text> [default: h5]
1121 Header style to use. Possible values: h1 to h6. This option is only used during the
1122 generation of a HTML table file.
1123 --highlightSMARTS <SMARTS> [default: none]
1124 SMARTS pattern for highlighting atoms and bonds in molecules. All matched
1125 substructures are highlighted.
1126 -i, --infile <infile>
1127 Input file name.
1128 --infileParams <Name,Value,...> [default: auto]
1129 A comma delimited list of parameter name and value pairs for reading
1130 molecules from files. The supported parameter names for different file
1131 formats, along with their default values, are shown below:
1132
1133 SD, MOL: removeHydrogens,yes,sanitize,yes,strictParsing,yes
1134 SMILES: smilesColumn,1,smilesNameColumn,2,smilesDelimiter,space,
1135 smilesTitleLine,auto,sanitize,yes
1136
1137 Possible values for smilesDelimiter: space, comma or tab.
1138 -k, --kekulize <yes or no> [default: yes]
1139 Perform kekulization on molecules. This option is ignored during the generation of
1140 a SVG and HTML output file.
1141 --keysNavigation <yes or no> [default: yes]
1142 Provide Excel like keyboard cell navigation for the table. This option is only used
1143 during the generation of a HTML table file.
1144 -m, --molImageSize <width,height> [default: 250,200]
1145 Image size of a molecule in pixels.
1146 --molImageEncoded <yes or no> [default: yes]
1147 Base64 encode SVG image of a molecule for inline embedding in a HTML page.
1148 The inline SVG image may fail to display in browsers without encoding.
1149 -n, --numOfMolsPerRow <number> [default: 4]
1150 Number of molecules to draw in a row.
1151 -o, --outfile <outfile>
1152 Output file name.
1153 --overwrite
1154 Overwrite existing files.
1155 -p, --paging <yes or no> [default: yes]
1156 Provide page navigation for browsing data in the table. This option is only used
1157 during the generation of a HTML table file.
1158 --pagingType <numbers, simple, ...> [default: full_numbers]
1159 Type of page navigation. Possible values: numbers, simple, simple_numbers,
1160 full, full_numbers, or first_last_numbers.
1161
1162 numbers - Page number buttons only
1163 simple - 'Previous' and 'Next' buttons only
1164 simple_numbers - 'Previous' and 'Next' buttons, plus page numbers
1165 full - 'First', 'Previous', 'Next' and 'Last' buttons
1166 full_numbers - 'First', 'Previous', 'Next' and 'Last' buttons, plus
1167 page numbers
1168 first_last_numbers - 'First' and 'Last' buttons, plus page numbers
1169
1170 This option is only used during the generation of a HTML table file.
1171 --pageLength <number> [default: 5]
1172 Number of rows to show per page. This option is only used during the
1173 generation of a HTML table file.
1174 --popover <yes or no> [default: yes]
1175 Display a popover window containing additional information about the
1176 molecule. The popover is opened after a click on the drawing of a
1177 molecule. A subsequent click on the same drawing closes the popover.
1178 This option is only used during the generation of a HTML table file.
1179 --popoverDataCount <number> [default: 25]
1180 Maximum number of data fields to show in a popover window. This option is
1181 only used during the generation of a HTML table file.
1182 --popoverTextWidth <number> [default: 50]
1183 Maximum width in characters for text display in a popover window before
1184 truncating the text. This option is only used during the generation of a HTML
1185 table file.
1186 -s, --showMolName <yes or no> [default: yes]
1187 Show molecule names under the images.This option is only used during the
1188 generation of a HTML table file.
1189 --scrollX <yes or no> [default: yes]
1190 Provide horizontal scroll bar in the table as needed.This option is only used
1191 during the generation of a HTML table file.
1192 --scrollY <yes or no> [default: yes]
1193 Provide vertical scroll bar in the table as needed.This option is only used during
1194 the generation of a HTML table file.
1195 --scrollYSize <number> [default: 75vh]
1196 Maximum height of table viewport either in pixels or percentage of the browser
1197 window height before providing a vertical scroll bar. Default: 75% of the height of
1198 browser window.This option is only used during the generation of a HTML table file.
1199 -t, --tableStyle <table,table-striped,...> [default: table,table-hover,table-sm]
1200 Style of table. Possible values: table, table-striped, table-bordered,
1201 table-hover, table-dark, table-sm, none, or All. Default: 'table,table-hover'. A
1202 comma delimited list of any valid Bootstrap table styles is also supported
1203
1204 This option is only used during the generation of a HTML table file.
1205 --tableFooter <yes or no> [default: yes]
1206 Show Excel style column headers at the end of the table. This option is only
1207 used during the generation of a HTML table file.
1208 --tableHeader <yes or no> [default: yes]
1209 Show Excel style column headers in the table. This option is only used
1210 during the generation of a HTML table file.
1211 --tableHeaderStyle <thead-dark,thead-light,...> [default: thead-dark]
1212 Style of table header. Possible values: thead-dark, thead-light, or none.
1213 The names of the following contextual color classes are also supported:
1214 table-primary (Blue), table-success (Green), table-danger (Red), table-info
1215 (Light blue), table-warning (Orange), table-active (Grey), table-light (Light
1216 grey), and table-dark (Dark grey).
1217
1218 This option is only used during the generation of a HTML table file.
1219 -w, --workingdir <dir>
1220 Location of working directory which defaults to the current directory.
1221
1222 Examples:
1223 To automatically compute 2D coordinates for molecules in a SMILES file and
1224 generate a SVG image file containing 4 molecules per row in a grid with cell
1225 size of 250 x 200 pixels, type:
1226
1227 % RDKitDrawMolecules.py -i Sample.smi -o SampleOut.svg
1228
1229 To automatically compute 2D coordinates for molecules in a SMILES file and
1230 generate a SVG image file containing 2 molecules per row in a grid with cell
1231 size of 400 x 300 pixels and without any keulization along with highlighting
1232 a specific set of atoms and bonds indicated by a SMARTS pattern, type:
1233
1234 % RDKitDrawMolecules.py -n 2 -m "400,300" -k no --fontBold no
1235 --highlightSMARTS 'c1ccccc1' -i Sample.smi -o SampleOut.svg
1236
1237 To generate a PNG image file for molecules in a SD file using existing 2D
1238 coordinates, type
1239
1240 % RDKitDrawMolecules.py --compute2DCoords no -i Sample.sdf
1241 -o SampleOut.png
1242
1243 To automatically compute 2D coordinates for molecules in a SD file and
1244 generate a HTML file containing 4 molecules per row in a table, along with
1245 all the bells and whistles to interact with the table, type:
1246
1247 % RDKitDrawMolecules.py -i Sample.sdf -o SampleOut.html
1248
1249 To automatically compute 2D coordinates for molecules in a SD file and
1250 generate a HTML file containing 4 molecules per row in a table without
1251 any bells and whistles to interact with the table, type:
1252
1253 % RDKitDrawMolecules.py --counterCol no --colVisibility no
1254 --keysNavigation no --paging no --popover no --scrollX no
1255 --scrollY no --tableFooter no --tableHeader no -i Sample.sdf
1256 -o SampleOut.html
1257
1258 To automatically compute 2D coordinates for molecules in a CSV SMILES file
1259 with column headers, SMILES strings in column 1, and name in column 2 and
1260 generate a PDF image file, type:
1261
1262 % RDKitDrawMolecules.py --infileParams "smilesDelimiter,comma,
1263 smilesTitleLine,yes,smilesColumn,1,smilesNameColumn,2"
1264 -i SampleSMILES.csv -o SampleOut.pdf
1265
1266 Author:
1267 Manish Sud(msud@san.rr.com)
1268
1269 See also:
1270 RDKitConvertFileFormat.py, RDKitDrawMoleculesAndDataTable.py, RDKitRemoveDuplicateMolecules.py,
1271 RDKitSearchFunctionalGroups.py, RDKitSearchSMARTS.py
1272
1273 Copyright:
1274 Copyright (C) 2026 Manish Sud. All rights reserved.
1275
1276 The functionality available in this script is implemented using RDKit, an
1277 open source toolkit for cheminformatics developed by Greg Landrum.
1278
1279 This file is part of MayaChemTools.
1280
1281 MayaChemTools is free software; you can redistribute it and/or modify it under
1282 the terms of the GNU Lesser General Public License as published by the Free
1283 Software Foundation; either version 3 of the License, or (at your option) any
1284 later version.
1285
1286 """
1287
1288 if __name__ == "__main__":
1289 main()