def bridge(coords, ring, isec): r = deque(ring) while r[0] not in isec or r[-1] in isec: r.rotate() v = r.popleft() u = None while True: p = r.popleft() if p in isec: u = p else: break r.appendleft(p) # TODO: refactor ux, uy, ua, _ = coords[u] vx, vy, va, _ = coords[v] utov = gm.vector((ux, uy), (vx, vy)) utovang = gm.rad(math.atan2(utov[1], utov[0])) dist = gm.distance((ux, uy), (vx, vy)) step, angle = bridge_angle(dist, len(r)) angle += utovang prev = u coords[prev][2] = gm.rad(utovang + math.pi) # reverse while r: tail = r.popleft() x, y, a, _ = coords[prev] coords[tail] = [x + math.cos(angle), y + math.sin(angle), gm.rad(a + step), 1] angle += step prev = tail
def draw_dashed_wedge(self, head, tail, color): width = self.displaymlb * 0.2 t1 = m_seg(tail, head, math.pi / -2, width / 2)[0] t2 = m_seg(tail, head, math.pi / 2, width / 2)[0] step_num = 7 for i in range(step_num): step = distance(head, tail) / step_num * i trim = 1 / step_num * i tp1, tp2 = p_seg(t1, t2, True, step, trim) tpx, tpy = [tuple(p) for p in zip(tp1, tp2)] self.ax.add_line(mpl.lines.Line2D( tpx, tpy, c=color, lw=1.5, clip_on=False ))
def draw_dashed_wedge(self, head, tail, color): width = self.displaymlb * 0.2 tmpl = '<line x1="{}" y1="{}" x2="{}" y2="{}" stroke="{}" />\n' t1 = m_seg(tail, head, math.pi / -2, width / 2)[0] t2 = m_seg(tail, head, math.pi / 2, width / 2)[0] step_num = 7 for i in range(step_num): step = distance(head, tail) / step_num * i trim = 1 / step_num * i tp1, tp2 = p_seg(t1, t2, True, step, trim) self._elems.append( tmpl.format(round(tp1[0], 2), round(tp1[1], 2), round(tp2[0], 2), round(tp2[1], 2), color))
def _draw_dashed_wedge(self, head, tail, width, color=(0, 0, 0)): chead = self._convert(head) ctail = self._convert(tail) b1 = m_seg(ctail, chead, math.pi / -2, width / 2)[0] b2 = m_seg(ctail, chead, math.pi / 2, width / 2)[0] pen = QtGui.QPen(QtGui.QColor(*color), 1, QtCore.Qt.SolidLine) self.painter.setPen(pen) step_num = 6 # TODO: adjust step number for i in range(step_num): step = distance(chead, ctail) / step_num * i trim = 1 / step_num * i bp1, bp2 = p_seg(b1, b2, True, step, trim) qp1 = QtCore.QPointF(*bp1) qp2 = QtCore.QPointF(*bp2) self.painter.drawLine(qp1, qp2)
def scale_and_center(mol): """Center and Scale molecule 2D coordinates. This method changes mol coordinates directly to center but not scale. This method returns width, height and MLB(median length of bond) and scaling will be done by drawer method with these values. Returns: width: float height: float mlb: median length of bond """ cnt = mol.atom_count() if cnt < 2: mol.size2d = (0, 0, 1) mol.descriptors.add("ScaleAndCenter") return xs = [] ys = [] for _, atom in mol.atoms_iter(): xs.append(atom.coords[0]) ys.append(atom.coords[1]) xmin, xmax = (min(xs), max(xs)) ymin, ymax = (min(ys), max(ys)) width = xmax - xmin height = ymax - ymin x_offset = width / 2 + xmin y_offset = height / 2 + ymin dists = [] for u, v, _ in mol.bonds_iter(): dists.append(geometry.distance(mol.atom(u).coords, mol.atom(v).coords)) try: mlb = statistics.median(dists) except statistics.StatisticsError: # No connection mlb = math.sqrt(max([width, height]) / cnt) # empirical if not mlb: # Many of connected atoms are overlapped mol.size2d = (0, 0, 1) mol.descriptors.add("ScaleAndCenter") return # Centering for _, atom in mol.atoms_iter(): atom.coords = (atom.coords[0] - x_offset, atom.coords[1] - y_offset) mol.size2d = (width, height, mlb) mol.descriptors.add("ScaleAndCenter")
def test_length(self): o = (2, 2) p1 = (5, 6) p2 = (2, 2) self.assertEqual(gm.distance(p1, o), 5) self.assertEqual(gm.distance(p2, o), 0)
def draw(canvas, mol): """Draw molecule structure image. Args: canvas: draw.drawable.Drawable mol: model.graphmol.Compound """ mol.require("ScaleAndCenter") mlb = mol.size2d[2] if not mol.atom_count(): return bond_type_fn = { 1: { 0: single_bond, 1: wedged_single, 2: dashed_wedged_single, 3: wave_single, }, 2: { 0: cw_double, 1: counter_cw_double, 2: double_bond, 3: cross_double }, 3: { 0: triple_bond } } # Draw bonds for u, v, bond in mol.bonds_iter(): if not bond.visible: continue if (u < v) == bond.is_lower_first: f, s = (u, v) else: s, f = (u, v) p1 = mol.atom(f).coords p2 = mol.atom(s).coords if p1 == p2: continue # avoid zero division if mol.atom(f).visible: p1 = gm.t_seg(p1, p2, F_AOVL, 2)[0] if mol.atom(s).visible: p2 = gm.t_seg(p1, p2, F_AOVL, 1)[1] color1 = mol.atom(f).color color2 = mol.atom(s).color bond_type_fn[bond.order][bond.type](canvas, p1, p2, color1, color2, mlb) # Draw atoms for n, atom in mol.atoms_iter(): if not atom.visible: continue p = atom.coords color = atom.color # Determine text direction if atom.H_count: cosnbrs = [] hrzn = (p[0] + 1, p[1]) for nbr in mol.graph.neighbors(n): pnbr = mol.atom(nbr).coords try: cosnbrs.append( gm.dot_product(hrzn, pnbr, p) / gm.distance(p, pnbr)) except ZeroDivisionError: pass if not cosnbrs or min(cosnbrs) > 0: # [atom]< or isolated node(ex. H2O, HCl) text = atom.formula_html(True) canvas.draw_text(p, text, color, "right") continue elif max(cosnbrs) < 0: # >[atom] text = atom.formula_html() canvas.draw_text(p, text, color, "left") continue # -[atom]- or no hydrogens text = atom.formula_html() canvas.draw_text(p, text, color, "center")