1import sys
2import os
3import argparse
4
5import CDPL.Chem as Chem
6import CDPL.ConfGen as ConfGen
7
8
9# generates a low-energy 3D structure of the argument molecule using
10# the provided initialized ConfGen.StructureGenerator instance
11def gen3DStructure(mol: Chem.Molecule, struct_gen: ConfGen.StructureGenerator) -> int:
12 # prepare the molecule for 3D structure generation
13 ConfGen.prepareForConformerGeneration(mol)
14
15 # generate the 3D structure
16 status = struct_gen.generate(mol)
17
18 # if sucessful, store the generated conformer ensemble as
19 # per atom 3D coordinates arrays (= the way conformers are represented in CDPKit)
20 if status == ConfGen.ReturnCode.SUCCESS:
21 struct_gen.setCoordinates(mol)
22
23 # return status code
24 return status
25
26def main() -> None:
27 args = parseArgs()
28
29 # create reader for input molecules (format specified by file extension)
30 reader = Chem.MoleculeReader(args.in_file)
31
32 # create writer for the generated 3D structures (format specified by file extension)
33 writer = Chem.MolecularGraphWriter(args.out_file)
34
35 # export only a single 3D structure (in case of multi-conf. input molecules)
36 Chem.setMultiConfExportParameter(writer, False)
37
38 # create and initialize an instance of the class ConfGen.StructureGenerator which will
39 # perform the actual 3D structure generation work
40 struct_gen = ConfGen.StructureGenerator()
41
42 struct_gen.settings.timeout = args.max_time * 1000 # apply the -t argument
43
44 # dictionary mapping status codes to human readable strings
45 status_to_str = { ConfGen.ReturnCode.UNINITIALIZED : 'uninitialized',
46 ConfGen.ReturnCode.TIMEOUT : 'max. processing time exceeded',
47 ConfGen.ReturnCode.ABORTED : 'aborted',
48 ConfGen.ReturnCode.FORCEFIELD_SETUP_FAILED : 'force field setup failed',
49 ConfGen.ReturnCode.FORCEFIELD_MINIMIZATION_FAILED : 'force field structure refinement failed',
50 ConfGen.ReturnCode.FRAGMENT_LIBRARY_NOT_SET : 'fragment library not available',
51 ConfGen.ReturnCode.FRAGMENT_CONF_GEN_FAILED : 'fragment conformer generation failed',
52 ConfGen.ReturnCode.FRAGMENT_CONF_GEN_TIMEOUT : 'fragment conformer generation timeout',
53 ConfGen.ReturnCode.FRAGMENT_ALREADY_PROCESSED : 'fragment already processed',
54 ConfGen.ReturnCode.TORSION_DRIVING_FAILED : 'torsion driving failed',
55 ConfGen.ReturnCode.CONF_GEN_FAILED : 'conformer generation failed' }
56
57 # create an instance of the default implementation of the Chem.Molecule interface
58 mol = Chem.BasicMolecule()
59 i = 1
60
61 # read and process molecules one after the other until the end of input has been reached
62 try:
63 while reader.read(mol):
64 # compose a simple molecule identifier
65 mol_id = Chem.getName(mol).strip()
66
67 if mol_id == '':
68 mol_id = '#' + str(i) # fallback if name is empty
69 else:
70 mol_id = '\'%s\' (#%s)' % (mol_id, str(i))
71
72 if not args.quiet:
73 print('- Generating 3D structure of molecule %s...' % mol_id)
74
75 try:
76 # generate 3D structure of the read molecule
77 status = gen3DStructure(mol, struct_gen)
78
79 # check for severe error reported by status code
80 if status != ConfGen.ReturnCode.SUCCESS:
81 if args.quiet:
82 print('Error: 3D structure generation for molecule %s failed: %s' % (mol_id, status_to_str[status]))
83 else:
84 print(' -> 3D structure generation failed: %s' % status_to_str[status])
85 else:
86 # enforce the output of 3D coordinates in case of MDL file formats
87 Chem.setMDLDimensionality(mol, 3)
88
89 # output the generated 3D structure
90 if not writer.write(mol):
91 sys.exit('Error: writing 3D structure of molecule %s failed' % mol_id)
92
93 except Exception as e:
94 sys.exit('Error: 3D structure generation or output for molecule %s failed: %s' % (mol_id, str(e)))
95
96 i += 1
97
98 except Exception as e: # handle exception raised in case of severe read errors
99 sys.exit('Error: reading molecule failed: ' + str(e))
100
101 writer.close()
102 sys.exit(0)
103
104def parseArgs() -> argparse.Namespace:
105 parser = argparse.ArgumentParser(description='Generates conformer ensembles for the given input molecules.')
106
107 parser.add_argument('-i',
108 dest='in_file',
109 required=True,
110 metavar='<file>',
111 help='Molecule input file')
112 parser.add_argument('-o',
113 dest='out_file',
114 required=True,
115 metavar='<file>',
116 help='Conformer ensemble output file')
117 parser.add_argument('-t',
118 dest='max_time',
119 required=False,
120 metavar='<int>',
121 type=int,
122 default=3600,
123 help='Max. allowed molecule processing time (default: 3600 sec)')
124 parser.add_argument('-q',
125 dest='quiet',
126 required=False,
127 action='store_true',
128 default=False,
129 help='Disable progress output (default: false)')
130
131 return parser.parse_args()
132
133if __name__ == '__main__':
134 main()