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