|
16 | 16 | from AaronTools import addlogger
|
17 | 17 | from AaronTools.atoms import Atom, BondOrder
|
18 | 18 | from AaronTools.config import Config
|
19 |
| -from AaronTools.const import AARONLIB, AARONTOOLS, BONDI_RADII, D_CUTOFF, ELEMENTS, TMETAL, VDW_RADII |
| 19 | +from AaronTools.const import AARONLIB, AARONTOOLS, BONDI_RADII, D_CUTOFF, ELEMENTS, TMETAL, VDW_RADII, RADII |
20 | 20 | from AaronTools.fileIO import FileReader, FileWriter, read_types
|
21 | 21 | from AaronTools.finders import Finder, OfType, WithinRadiusFromPoint, WithinRadiusFromAtom
|
22 | 22 | from AaronTools.utils.prime_numbers import Primes
|
@@ -1013,6 +1013,92 @@ def convert_to_Psi4(self, charge=0, mult=1, fix_com=True, fix_orientation=True):
|
1013 | 1013 | return mol
|
1014 | 1014 |
|
1015 | 1015 |
|
| 1016 | + # quick and dirty code to display HoukMol style figures in Matplotlib |
| 1017 | + # bond ends could be handled more elegently, but this looks fine for most molecules |
| 1018 | + |
| 1019 | + def plot(self, ax, fig, fp=40, ascale=0.5, bwidth=0.15): |
| 1020 | + """ |
| 1021 | + displays HoukMol style molecule in Matplotlib |
| 1022 | +
|
| 1023 | + :param matplotlib.pyplot.Axis ax: Matplotlib Axis object |
| 1024 | + :param matplotlib.pyplot.Figure fig: Matplotlib Figure object:w |
| 1025 | + :param float fp: z-value (Angstroms) for focal point for adding perspective (Default: 40) |
| 1026 | + :param float ascale: scaling factor for covalent radii (default: 0.5) |
| 1027 | + :param float bwidth: scaling factor for bond radii (default: 0.15) |
| 1028 | + """ |
| 1029 | + |
| 1030 | + try: |
| 1031 | + import matplotlib.patches as patches |
| 1032 | + except: |
| 1033 | + self.LOG.error("Must install matplot lib") |
| 1034 | + return None |
| 1035 | + |
| 1036 | + def intersect(atom1, atom2, fp): |
| 1037 | + # returns intersection of line from atom1 to edge of scaled sphere for atom2 |
| 1038 | + # from https://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection |
| 1039 | + |
| 1040 | + c = atom2.coords # center of sphere |
| 1041 | + r = RADII[atom2.element]*0.5*(fp + atom2.coords[2])/fp # radius of sphere, adjusted for perspective |
| 1042 | + o = atom1.coords # starting point for line |
| 1043 | + u = atom1.bond(atom2) # vector along line |
| 1044 | + unorm = u/np.sqrt(np.linalg.norm(u)) # normalized vector |
| 1045 | + |
| 1046 | + dot = np.dot(unorm, o - c) |
| 1047 | + delta = dot**2 - (np.linalg.norm(o - c)**2 - r**2) |
| 1048 | + |
| 1049 | + if delta <= 0: |
| 1050 | + # catch cases where there is no intersection for some reason |
| 1051 | + return c |
| 1052 | + else: |
| 1053 | + d = dot + np.sqrt(delta) |
| 1054 | + return o - d*u*1.0 # adjust endpoints slightly to account for rounded capstyle |
| 1055 | + |
| 1056 | + |
| 1057 | + |
| 1058 | + def draw_bond(x1, y1, x2, y2, z, fp, scale, ax, bwidth=0.15): |
| 1059 | + """ |
| 1060 | + Draw HoukMol style bond as black line with rounded ends from (x1, y1) to (x2, y2) |
| 1061 | + width of bond is controlled by width and scaled to z-value for perspective |
| 1062 | + """ |
| 1063 | + |
| 1064 | + w=(bwidth*scale) * (fp + z)/fp |
| 1065 | + ax.plot([x1, x2], [y1, y2], lw=w, color='black', solid_capstyle='round', zorder=z) |
| 1066 | + |
| 1067 | + |
| 1068 | + # if xlim not set manually then I can't determine the overall size of the plot until |
| 1069 | + # after the molecule is drawn, but I need overall size to determine scale for bond |
| 1070 | + # widths |
| 1071 | + |
| 1072 | + if ax.get_xaxis()._get_autoscale_on(): |
| 1073 | + self.LOG.warning("You will probalby need to set xlim manually for correct bond widths.") |
| 1074 | + |
| 1075 | + # get scale and size of plot set linewidths in terms of pts (1/72 inch per pt) |
| 1076 | + xmin, xmax = ax.get_xlim() |
| 1077 | + dx = xmax - xmin |
| 1078 | + ymin, ymax = ax.get_ylim() |
| 1079 | + dy = ymax - ymin |
| 1080 | + fw, fh = fig.get_size_inches() |
| 1081 | + bbox = ax.get_position() |
| 1082 | + ax_width = fw*bbox.width |
| 1083 | + scale = 72*ax_width/dx |
| 1084 | + |
| 1085 | + # sort atoms by z-value |
| 1086 | + sorted_atoms = [atom for atom in sorted(self.atoms, key=lambda a: a.coords[2])] |
| 1087 | + |
| 1088 | + for atom in sorted_atoms: |
| 1089 | + # note that I draw each bond twice to get both ends correct |
| 1090 | + # this avoids having to do the logic of figuring out the order of |
| 1091 | + # drawing bonds to a given atom--each atom obscures bonds originating |
| 1092 | + # from that atom, but this is corrected when the bond is drawn |
| 1093 | + # from the connected atom. Any way I've tried to fix this looks wrong |
| 1094 | + # for planar molecules, which are the only ones I actually care about. |
| 1095 | + |
| 1096 | + for connected in atom.connected: |
| 1097 | + endpoint = intersect(atom, connected, fp) |
| 1098 | + draw_bond(atom.coords[0], atom.coords[1], endpoint[0], endpoint[1], atom.coords[2], fp, scale, ax, bwidth) |
| 1099 | + |
| 1100 | + atom.draw_atom(ax, fp, ascale=ascale, linewidth=0.01*scale) |
| 1101 | + |
1016 | 1102 | def copy(self, atoms=None, name=None, comment=None, copy_atoms=True):
|
1017 | 1103 | """
|
1018 | 1104 | creates a new copy of the geometry
|
|
0 commit comments