1.1.4.4. 2D Structure Generation
The script gen_2d_layout.py reads molecules from a given input file, computes a 2D layout and writes the result to the specified output file.
Synopsis
python gen_2d_layout.py [-h] -i <file> -o <file> [-d] [-c] [-q]
Mandatory options
- -i <file>
Molecule input file
- -o <file>
Laid out molecule output file
Other options
- -h, --help
Show help message and exit
- -d
Remove ordinary explicit hydrogens (default: false)
- -c
Saturate free valences with explicit hydrogens (default: false)
- -q
Disable progress output (default: false)
Code
1import sys
2import argparse
3
4import CDPL.Chem as Chem
5import CDPL.Math as Math
6import CDPL.Util as Util
7
8
9# computes a 2D structure layout of the argument molecule using the provided
10# Chem.Atom2DCoordinatesCalculator and Chem.BondStereoFlagCalculator instances
11def gen2DLayout(mol: Chem.Molecule, coords_calc: Chem.Atom2DCoordinatesCalculator, bs_flags_calc: Chem.BondStereoFlagCalculator, args: argparse.Namespace) -> None:
12 # calculate several properties required for 2D structure layout
13 Chem.calcBasicProperties(mol, False)
14 Chem.calcCIPPriorities(mol, False)
15 Chem.perceiveAtomStereoCenters(mol, False, True)
16 Chem.perceiveBondStereoCenters(mol, False, True)
17 Chem.calcAtomStereoDescriptors(mol, False)
18 Chem.calcBondStereoDescriptors(mol, False)
19
20 changes = False
21
22 if args.rem_h: # remove ordinary (with standard form. charge, isotope, connectivity) hydrogens, if desired
23 changes = Chem.makeOrdinaryHydrogenDeplete(mol, Chem.AtomPropertyFlag.ISOTOPE | Chem.AtomPropertyFlag.FORMAL_CHARGE | Chem.AtomPropertyFlag.EXPLICIT_BOND_COUNT, True)
24 elif args.add_h: # make hydrogen complete, if desired
25 changes = Chem.makeHydrogenComplete(mol)
26
27 if changes: # if expl. hydrogen count has changed -> recompute dependent properties
28 Chem.perceiveComponents(mol, True)
29 Chem.calcTopologicalDistanceMatrix(mol, True)
30 Chem.calcAtomStereoDescriptors(mol, True, 0, False)
31 Chem.calcBondStereoDescriptors(mol, True, 0, False)
32 else:
33 Chem.calcTopologicalDistanceMatrix(mol, False)
34
35 coords = Math.Vector2DArray() # will store the computed 2D coordinates
36 bs_flags = Util.UIArray() # will store the computed bond stereo flags (up, down) specifying atom (and bond) configurations
37
38 # calculate 2D coordinates
39 coords_calc.calculate(mol, coords)
40
41 # store calculated 2D coordinates as corr. atom properties
42 Chem.set2DCoordinates(mol, coords)
43
44 # calculate bond stereo flags for the given 2D coordinates
45 bs_flags_calc.calculate(mol, bs_flags)
46
47 # store bond stereo flags as corr. bond properties
48 for i in range(mol.numBonds):
49 Chem.set2DStereoFlag(mol.getBond(i), bs_flags[i])
50
51def parseArgs() -> argparse.Namespace:
52 parser = argparse.ArgumentParser(description='Performs a 2D structure layout for the given input molecules.')
53
54 parser.add_argument('-i',
55 dest='in_file',
56 required=True,
57 metavar='<file>',
58 help='Molecule input file')
59 parser.add_argument('-o',
60 dest='out_file',
61 required=True,
62 metavar='<file>',
63 help='Laid out molecule output file')
64 parser.add_argument('-d',
65 dest='rem_h',
66 required=False,
67 action='store_true',
68 default=False,
69 help='Remove ordinary explicit hydrogens (default: false)')
70 parser.add_argument('-c',
71 dest='add_h',
72 required=False,
73 action='store_true',
74 default=False,
75 help='Saturate free valences with explicit hydrogens (default: false)')
76 parser.add_argument('-q',
77 dest='quiet',
78 required=False,
79 action='store_true',
80 default=False,
81 help='Disable progress output (default: false)')
82
83 return parser.parse_args()
84
85def main() -> None:
86 args = parseArgs()
87
88 # create reader for input molecules (format specified by file extension)
89 reader = Chem.MoleculeReader(args.in_file)
90
91 # create writer for the generated 3D structures (format specified by file extension)
92 writer = Chem.MolecularGraphWriter(args.out_file)
93
94 # create and initialize an instance of the class Chem.Atom2DCoordinatesCalculator which will
95 # perform the 2D coordinates calculation
96 coords_calc = Chem.Atom2DCoordinatesCalculator()
97
98 # create and initialize an instance of the class Chem.BondStereoFlagsCalculator which will
99 # perform the calculation 2D bond stereo flags (= up, down, ...)
100 bs_flags_calc = Chem.BondStereoFlagCalculator()
101
102 # create an instance of the default implementation of the Chem.Molecule interface
103 mol = Chem.BasicMolecule()
104 i = 1
105
106 # read and process molecules one after the other until the end of input has been reached
107 try:
108 while reader.read(mol):
109 # compose a simple molecule identifier
110 mol_id = Chem.getName(mol).strip()
111
112 if mol_id == '':
113 mol_id = '#' + str(i) # fallback if name is empty
114 else:
115 mol_id = f'\'{mol_id}\' (#{i})'
116
117 if not args.quiet:
118 print(f'- Computing 2D layout of molecule {mol_id}...')
119
120 try:
121 # generate a 2D structure layout of the read molecule
122 gen2DLayout(mol, coords_calc, bs_flags_calc, args)
123
124 # enforce the output of 2D coordinates in case of MDL file formats
125 Chem.setMDLDimensionality(mol, 2)
126
127 # output the laid out molecule
128 if not writer.write(mol):
129 sys.exit(f'Error: writing molecule {mol_id} failed')
130
131 except Exception as e:
132 sys.exit(f'Error: 2D structure layout generation or output for molecule {mol_id} failed: {str(e)}')
133
134 i += 1
135
136 except Exception as e: # handle exception raised in case of severe read errors
137 sys.exit(f'Error: reading molecule failed: {str(e)}')
138
139 writer.close()
140 sys.exit(0)
141
142if __name__ == '__main__':
143 main()