def don_water(donor, donor_hyds, acceptor, sp2_O_rp2, sp2_O_r2, sp2_O_theta, sp3_O_rp2, sp3_O_r2, sp3_O_theta, sp3_O_phi, sp3_N_rp2, sp3_N_r2, sp3_N_theta, sp3_N_upsilon, gen_rp2, gen_r2, gen_theta): if hbond.verbose: print("don_water") if len(donor_hyds) > 0: # hydrogens explicitly present, can immediately call don_theta_tau return don_theta_tau(donor, donor_hyds, acceptor, sp2_O_rp2, sp2_O_theta, sp3_O_rp2, sp3_O_theta, sp3_O_phi, sp3_N_rp2, sp3_N_theta, sp3_N_upsilon, gen_rp2, gen_theta) ap = acceptor._hb_coord dp = donor._hb_coord acc_type = acceptor.idatm_type if acc_type not in type_info: if hbond.verbose: print("Unknown acceptor type failure") return False geom = type_info[acc_type].geometry element = acceptor.element.name if element == 'O' and geom == planar: if hbond.verbose: print("planar O") sq = distance_squared(dp, ap) if sq > sp2_O_r2: if hbond.verbose: print("dist criteria failed (%g > %g)" % (sqrt(sq), sqrt(sp2_O_r2))) return False elif element == 'O' and geom == tetrahedral or element == 'N' and geom == planar: if hbond.verbose: print("planar N or tet O") sq = distance_squared(dp, ap) if sq > sp3_O_r2: if hbond.verbose: print("dist criteria failed (%g > %g)" % (sqrt(sq), sqrt(sp3_O_r2))) return False elif element == 'N' and geom == tetrahedral: if hbond.verbose: print("tet N") sq = distance_squared(dp, ap) if sq > sp3_N_r2: if hbond.verbose: print("dist criteria failed (%g > %g)" % (sqrt(sq), sqrt(sp3_N_r2))) return False else: if hbond.verbose: print("generic acceptor") sq = distance_squared(dp, ap) if sq > gen_r2: if hbond.verbose: print("dist criteria failed (%g > %g)" % (sqrt(sq), sqrt(gen_r2))) return False if hbond.verbose: print("dist criteria OK") return don_theta_tau(donor, donor_hyds, acceptor, sp2_O_rp2, sp2_O_theta, sp3_O_rp2, sp3_O_theta, sp3_O_phi, sp3_N_rp2, sp3_N_theta, sp3_N_upsilon, gen_rp2, gen_theta, is_water=True)
def test_upsion_tau_acceptor(donor, donor_hyds, acceptor, r2, upsilon_low, upsilon_high, theta, tau, tau_sym): dc = donor._hb_coord ac = acceptor._hb_coord d2 = distance_squared(dc, ac) if d2 > r2: if hbond.verbose: print("dist criteria failed (%g > %g)" % (sqrt(d2), sqrt(r2))) return False upsilon_high = 0 - upsilon_high heavys = [a for a in donor.neighbors if a.element.number > 1] if len(heavys) != 1: raise AtomTypeError("upsilon tau donor (%s) not bonded to" " exactly one heavy atom" % donor) ang = angle(heavys[0]._hb_coord, dc, ac) if ang < upsilon_low or ang > upsilon_high: if hbond.verbose: print("upsilon criteria failed (%g < %g or %g > %g)" % (ang, upsilon_low, ang, upsilon_high)) return False if hbond.verbose: print("upsilon criteria OK (%g < %g < %g)" % (upsilon_low, ang, upsilon_high)) dp = dc ap = ac if not test_theta(dp, donor_hyds, ap, theta): return False return test_tau(tau, tau_sym, donor, dp, ap)
def _make_shared_data(session, protonation_models, in_isolation): from chimerax.geometry import distance_squared from chimerax.atom_search import AtomSearchTree # since adaptive search tree is static, it will not include # hydrogens added after this; they will have to be found by # looking off their heavy atoms global search_tree, _radii, _metals, ident_pos_models, _h_coloring, _solvent_atoms _radii = {} search_atoms = [] metal_atoms = [] # if we're adding hydrogens to unopen models, add those models to open models... pm_set = set(protonation_models) if in_isolation: models = pm_set else: from chimerax.atomic import AtomicStructure om_set = set( [m for m in session.models if isinstance(m, AtomicStructure)]) models = om_set | pm_set # consider only one copy of identically-positioned models... ident_pos_models = {} for pm in protonation_models: for m in models: if m == pm: continue if pm.num_atoms != m.num_atoms: continue for a1, a2 in zip(pm.atoms[:3], m.atoms[:3]): if distance_squared(a1._addh_coord, a2._addh_coord) > 0.00001: break else: ident_pos_models.setdefault(pm, set()).add(m) for m in models: if m not in ident_pos_models: ident_pos_models[m] = set() for a in m.atoms: search_atoms.append(a) _radii[a] = a.radius if a.element.is_metal: metal_atoms.append(a) from chimerax.atomic import Atom use_scene_coords = Atom._addh_coord == Atom.scene_coord search_tree = AtomSearchTree(search_atoms, sep_val=_tree_dist, scene_coords=use_scene_coords) _metals = AtomSearchTree(metal_atoms, sep_val=max(_metal_dist, 1.0), scene_coords=use_scene_coords) from weakref import WeakKeyDictionary _h_coloring = WeakKeyDictionary() _solvent_atoms = WeakKeyDictionary()
def evaluate(self, pos): sum = 0.0 n = 0 from chimerax.geometry import distance_squared for coords in self._gather_coords(pos): for i, crd1 in enumerate(coords): for crd2 in coords[i + 1:]: sum += distance_squared(crd1, crd2) n += (len(coords) * (len(coords) - 1)) // 2 if n == 0: return None from math import sqrt return sqrt(sum / n)
def don_generic(donor, donor_hyds, acceptor, sp2_O_rp2, sp3_O_rp2, sp3_N_rp2, sp2_O_r2, sp3_O_r2, sp3_N_r2, gen_rp2, gen_r2, min_hyd_angle, min_bonded_angle): if hbond.verbose: print("don_generic") dc = donor._hb_coord ac = acceptor._hb_coord acc_type = acceptor.idatm_type if acc_type not in type_info: return False geom = type_info[acc_type].geometry element = acceptor.element.name if element == 'O' and geom == planar: if hbond.verbose: print("planar O") r2 = sp2_O_r2 rp2 = sp2_O_rp2 elif element == 'O' and geom == tetrahedral or element == 'N' and geom == planar: if hbond.verbose: print("planar N or tet O") r2 = sp3_O_r2 rp2 = sp3_O_rp2 elif element == 'N' and geom == tetrahedral: if hbond.verbose: print("tet N") r2 = sp3_N_r2 rp2 = sp3_N_rp2 else: if hbond.verbose: print("generic acceptor") if acceptor.element.name == "S": r2 = sulphur_compensate(gen_r2) min_bonded_angle = min_bonded_angle - 9 r2 = gen_r2 rp2 = gen_rp2 ap = acceptor._hb_coord dp = donor._hb_coord if len(donor_hyds) == 0: d2 = distance_squared(dc, ac) if d2 > r2: if hbond.verbose: print("dist criteria failed (%g > %g)" % (sqrt(d2), sqrt(r2))) return False else: for hyd_pos in donor_hyds: if distance_squared(hyd_pos, ap) < rp2: break else: if hbond.verbose: print("hyd dist criteria failed (all >= %g)" % (sqrt(rp2))) return False if hbond.verbose: print("dist criteria OK") for bonded in donor.neighbors: if bonded.element.number <= 1: continue bp = bonded._hb_coord ang = angle(bp, dp, ap) if ang < min_bonded_angle: if hbond.verbose: print("bonded angle too sharp (%g < %g)" % (ang, min_bonded_angle)) return False if len(donor_hyds) == 0: if hbond.verbose: print("No specific hydrogen positions; default accept") return True for hyd_pos in donor_hyds: ang = angle(dp, hyd_pos, ap) if ang >= min_hyd_angle: if hbond.verbose: print("hydrogen angle okay (%g >= %g)" % (ang, min_hyd_angle)) return True if hbond.verbose: print("hydrogen angle(s) too sharp (< %g)" % min_hyd_angle) return False
def don_theta_tau(donor, donor_hyds, acceptor, sp2_O_rp2, sp2_O_theta, sp3_O_rp2, sp3_O_theta, sp3_O_phi, sp3_N_rp2, sp3_N_theta, sp3_N_upsilon, gen_rp2, gen_theta, is_water=False): # 'is_water' only for hydrogenless water if hbond.verbose: print("don_theta_tau") if len(donor_hyds) == 0 and not is_water: if hbond.verbose: print("No hydrogens; default failure") return False ap = acceptor._hb_coord dp = donor._hb_coord acc_type = acceptor.idatm_type if acc_type not in type_info: if hbond.verbose: print("Unknown acceptor type failure") return False geom = type_info[acc_type].geometry element = acceptor.element.name if element == 'O' and geom == planar: if hbond.verbose: print("planar O") for hyd_pos in donor_hyds: if distance_squared(hyd_pos, ap) <= sp2_O_rp2: break else: if not is_water: if hbond.verbose: print("dist criteria failed (all > %g)"% sqrt(sp2_O_rp2)) return False theta = sp2_O_theta elif element == 'O' and geom == tetrahedral or element == 'N' and geom == planar: if hbond.verbose: print("planar N or tet O") for hyd_pos in donor_hyds: if distance_squared(hyd_pos, ap) <= sp3_O_rp2: break else: if not is_water: if hbond.verbose: print("dist criteria failed (all > %g)" % sqrt(sp3_O_rp2)) return False theta = sp3_O_theta # only test phi for acceptors with two bonded atoms if acceptor.num_bonds == 2: if hbond.verbose: print("testing donor phi") bonded = acceptor.neighbors phi_plane, base_pos = get_phi_plane_params(acceptor, bonded[0], bonded[1]) if not test_phi(donor._hb_coord, ap, base_pos, phi_plane, sp3_O_phi): return False elif element == 'N' and geom == tetrahedral: if hbond.verbose: print("tet N") for hyd_pos in donor_hyds: if distance_squared(hyd_pos, ap) <= sp3_N_rp2: break else: if not is_water: if hbond.verbose: print("dist criteria failed (all > %g)" % sqrt(sp3_N_rp2)) return False theta = sp3_N_theta # test upsilon against lone pair directions bonded_pos = [] for bonded in acceptor.neighbors: bonded_pos.append(bonded._hb_coord) lp_pos = bond_positions(ap, geom, 1.0, bonded_pos) if len(lp_pos) > 0: # fixed lone pair positions for lp in bond_positions(ap, geom, 1.0, bonded_pos): # invert position so that we are measuring angles correctly ang = angle(dp, ap, ap - (lp - ap)) if ang > sp3_N_upsilon: if hbond.verbose: print("acceptor upsilon okay (%g > %g)" % (ang, sp3_N_upsilon)) break else: if hbond.verbose: print("all acceptor upsilons failed (< %g)" % sp3_N_upsilon) return False # else: indefinite lone pair positions; default okay else: if hbond.verbose: print("generic acceptor") if acceptor.element.name == "S": gen_rp2 = sulphur_compensate(gen_rp2) for hyd_pos in donor_hyds: if distance_squared(hyd_pos, ap) <= gen_rp2: break else: if hbond.verbose: print("dist criteria failed (all > %g)" % sqrt(gen_rp2)) return False theta = gen_theta if hbond.verbose: print("dist criteria OK") return test_theta(dp, donor_hyds, ap, theta)
def cmd_define_plane(session, atoms, *, thickness=defaults["plane_thickness"], padding=0.0, color=None, radius=None, name="plane"): """Wrapper to be called by command line. Use chimerax.axes_planes.plane for other programming applications. """ from chimerax.core.errors import UserError from chimerax.atomic import AtomicStructure, concatenate, Structure if atoms is None: structures_atoms = [ m.atoms for m in session.models if isinstance(m, AtomicStructure) ] if structures_atoms: atoms = concatenate(structures_atoms) else: raise UserError("Atom specifier selects no atoms") if len(atoms) < 3: raise UserError("Must specify at least 3 atoms to define a plane") structures = atoms.unique_structures if len(structures) > 1: crds = atoms.scene_coords else: crds = atoms.coords from chimerax.geometry import Plane, distance_squared plane = Plane(crds) if radius is None: max_sq_dist = None origin = plane.origin for crd in crds: projected = plane.nearest(crd) sq_dist = distance_squared(origin, projected) if max_sq_dist is None or sq_dist > max_sq_dist: max_sq_dist = sq_dist from math import sqrt radius = sqrt(max_sq_dist) if color is None: from chimerax.atomic.colors import element_color, predominant_color color = predominant_color(atoms) if color is None: color = element_color(a.element.number) else: color = color.uint8x4() plane_model = PlaneModel(session, name, plane, thickness, radius + padding, color) if len(structures) > 1: session.models.add([plane_model]) else: structures[0].add([plane_model]) session.logger.info("Plane %s' placed at %s with normal %s" % (name, plane.origin, plane.normal)) return plane_model
def cmd_hbonds(session, atoms, intra_model=True, inter_model=True, relax=True, dist_slop=rec_dist_slop, angle_slop=rec_angle_slop, two_colors=False, restrict="any", radius=AtomicStructure.default_hbond_radius, save_file=None, batch=False, inter_submodel=False, make_pseudobonds=True, retain_current=False, reveal=False, naming_style=None, log=False, cache_DA=None, color=AtomicStructure.default_hbond_color, slop_color=BuiltinColors["dark orange"], show_dist=False, intra_res=True, intra_mol=True, dashes=None, salt_only=False, name="hydrogen bonds", coordsets=True, select=False): """Wrapper to be called by command line. Use hbonds.find_hbonds for other programming applications. """ if atoms is None: from chimerax.atomic import concatenate structures_atoms = [ m.atoms for m in session.models if isinstance(m, AtomicStructure) ] if structures_atoms: atoms = concatenate(structures_atoms) else: atoms = Atoms() from chimerax.core.errors import UserError if not atoms and not batch: raise UserError("Atom specifier selects no atoms") bond_color = color if restrict == "both": donors = acceptors = atoms structures = atoms.unique_structures elif restrict in ["cross", "any"]: donors = acceptors = None if inter_model: structures = [ m for m in session.models if isinstance(m, AtomicStructure) ] else: structures = atoms.unique_structures else: # another Atoms collection if not restrict and not batch: raise UserError("'restrict' atom specifier selects no atoms") combined = atoms | restrict donors = acceptors = combined structures = combined.unique_structures if not relax: dist_slop = angle_slop = 0.0 base_kw = { 'inter_model': inter_model, 'intra_model': intra_model, 'donors': donors, 'acceptors': acceptors, 'inter_submodel': inter_submodel, 'cache_da': cache_DA } doing_coordsets = coordsets and len( structures) == 1 and structures[0].num_coordsets > 1 if doing_coordsets: hb_func = find_coordset_hbonds struct_info = structures[0] else: hb_func = find_hbonds struct_info = structures result = hb_func(session, struct_info, dist_slop=dist_slop, angle_slop=angle_slop, **base_kw) if doing_coordsets: hb_lists = result else: hb_lists = [result] for hbonds in hb_lists: # filter on salt bridges first, since we need access to all H-bonds in order # to assess which histidines should be considered salt-bridge donors if salt_only: sb_donors, sb_acceptors = salt_preprocess(hbonds) hbonds[:] = [ hb for hb in hbonds if hb[0] in sb_donors and hb[1] in sb_acceptors ] hbonds[:] = restrict_hbonds(hbonds, atoms, restrict) if not intra_mol: mol_num = 0 mol_map = {} for s in structures: for m in s.molecules: mol_num += 1 for a in m: mol_map[a] = mol_num hbonds[:] = [ hb for hb in hbonds if mol_map[hb[0]] != mol_map[hb[1]] ] if not intra_res: hbonds[:] = [hb for hb in hbonds if hb[0].residue != hb[1].residue] if doing_coordsets: cs_ids = structures[0].coordset_ids output_info = (inter_model, intra_model, relax, dist_slop, angle_slop, structures, hb_lists, cs_ids) else: output_info = (inter_model, intra_model, relax, dist_slop, angle_slop, structures, result, None) if log: import io buffer = io.StringIO() buffer.write("<pre>") _file_output(buffer, output_info, naming_style) buffer.write("</pre>") session.logger.info(buffer.getvalue(), is_html=True) if save_file is not None: _file_output(save_file, output_info, naming_style) if doing_coordsets: session.logger.status("%d hydrogen bonds found in %d coordsets" % (sum([len(hbs) for hbs in hb_lists]), len(cs_ids)), log=True, blank_after=120) else: session.logger.status("%d hydrogen bonds found" % len(result), log=True, blank_after=120) if select: if doing_coordsets: structure = structures[0] for i, cs_id in enumerate(structure.coordset_ids): if structure.active_coordset_id == cs_id: break hb_list = hb_lists[i] else: hb_list = hb_lists[0] cs_id = None session.selection.clear() for d, a in hb_list: if cs_id is None: acc_coord = a.scene_coord else: acc_coord = a.get_coordset_coord(cs_id) dist, hyd = donor_hyd(d, cs_id, acc_coord) if hyd is None: d.selected = True else: hyd.selected = True a.selected = True if not make_pseudobonds: return hb_lists if doing_coordsets else hb_lists[0] if two_colors: # color relaxed constraints differently precise_result = hb_func(session, struct_info, **base_kw) if doing_coordsets: precise_lists = precise_result else: precise_lists = [precise_result] for precise in precise_lists: precise[:] = restrict_hbonds(precise, atoms, restrict) if not intra_mol: precise[:] = [ hb for hb in precise if mol_map[hb[0]] != mol_map[hb[1]] ] if not intra_res: precise[:] = [ hb for hb in precise if hb[0].residue != hb[1].residue ] if salt_only: precise[:] = [ hb for hb in precise if hb[0] in sb_donors and hb[1] in sb_acceptors ] # give another opportunity to read the result... if doing_coordsets: session.logger.status( "%d strict hydrogen bonds found in %d coordsets" % (sum([len(hbs) for hbs in precise_lists]), len(cs_ids)), log=True, blank_after=120) else: session.logger.status("%d strict hydrogen bonds found" % len(precise_result), log=True, blank_after=120) # a true inter-model computation should be placed in a global group, otherwise # into individual per-structure groups submodels = False m_ids = set() for s in structures: m_id = s.id[:-1] if len(s.id) > 1 else s.id if m_id in m_ids: submodels = True m_ids.add(m_id) global_comp = (inter_model and len(m_ids) > 1) or (submodels and inter_submodel) if global_comp: # global comp nukes per-structure groups it covers if intra-model also if intra_model and not retain_current: closures = [] for s in structures: pbg = s.pseudobond_group(name, create_type=None) if pbg: closures.append(pbg) if closures: session.models.close(closures) hb_info = [(result, session.pb_manager.get_group(name))] elif doing_coordsets: hb_info = [(result, structures[0].pseudobond_group(name, create_type="coordset"))] else: per_structure = {s: [] for s in structures} for hb in result: per_structure[hb[0].structure].append(hb) hb_info = [(hbs, s.pseudobond_group(name, create_type="coordset")) for s, hbs in per_structure.items()] for grp_hbonds, pbg in hb_info: if not retain_current: pbg.clear() pbg.color = bond_color.uint8x4() pbg.radius = radius pbg.dashes = dashes if dashes is not None else AtomicStructure.default_hbond_dashes else: if dashes is not None: pbg.dashes = dashes if not doing_coordsets: grp_hbonds = [grp_hbonds] for i, cs_hbonds in enumerate(grp_hbonds): pre_existing = {} if doing_coordsets: cs_id = cs_ids[i] pbg_pseudobonds = pbg.get_pseudobonds(cs_id) else: pbg_pseudobonds = pbg.pseudobonds if two_colors: precise = set(precise_lists[i]) if retain_current: for pb in pbg_pseudobonds: pre_existing[pb.atoms] = pb from chimerax.geometry import distance_squared for don, acc in cs_hbonds: nearest = None heavy_don = don for h in [x for x in don.neighbors if x.element.number == 1]: sqdist = distance_squared(h.scene_coord, acc.scene_coord) if nearest is None or sqdist < nsqdist: nearest = h nsqdist = sqdist if nearest is not None: don = nearest if (don, acc) in pre_existing: pb = pre_existing[(don, acc)] else: if doing_coordsets: pb = pbg.new_pseudobond(don, acc, cs_id) else: pb = pbg.new_pseudobond(don, acc) if two_colors: if (heavy_don, acc) in precise: color = bond_color else: color = slop_color else: color = bond_color rgba = pb.color rgba[:3] = color.uint8x4()[:3] # preserve transparency pb.color = rgba pb.radius = radius if reveal: for end in [don, acc]: if end.display: continue res_atoms = end.residue.atoms if end.is_side_chain: res_atoms.filter(res_atoms.is_side_chains == True).displays = True elif end.is_backbone(): res_atoms.filter(res_atoms.is_backbones() == True).displays = True else: res_atoms.displays = True if pbg.id is None: session.models.add([pbg]) if show_dist: session.pb_dist_monitor.add_group(pbg) else: session.pb_dist_monitor.remove_group(pbg) return hb_lists if doing_coordsets else hb_lists[0]
def _alt_loc_add_hydrogens(atom, alt_loc_atom, bonding_info, naming_schema, total_hydrogens, idatm_type, invert, coordinations): from .cmd import new_hydrogen, find_nearest, roomiest, find_rotamer_nearest, add_altloc_hyds from .util import bond_with_H_length away = away2 = planar = None geom = bonding_info.geometry substs = bonding_info.substituents needed = substs - atom.num_explicit_bonds if needed <= 0: return added = None if alt_loc_atom is None: alt_locs = [atom.alt_loc] else: alt_locs = alt_loc_atom.alt_locs # move current alt_loc to end of list to minimize # the number of times we have to change alt locs cur_alt_loc = alt_loc_atom.alt_loc alt_locs.remove(cur_alt_loc) alt_locs.append(cur_alt_loc) alt_loc_info = [] for alt_loc in alt_locs: if alt_loc_atom: alt_loc_atom.alt_loc = alt_loc occupancy = alt_loc_atom.occupancy else: occupancy = 1.0 at_pos = atom._addh_coord exclude = coordinations + list(atom.neighbors) if geom == 3: if atom.num_bonds == 1: bonded = atom.neighbors[0] grand_bonded = list(bonded.neighbors) grand_bonded.remove(atom) if len(grand_bonded) < 3: planar = [a._addh_coord for a in grand_bonded] if geom == 4 and atom.num_bonds == 0: away, d, natom = find_nearest(at_pos, atom, exclude, 3.5) if away is not None: away2, d2, natom2 = find_rotamer_nearest( at_pos, idatm_type[atom], atom, natom, 3.5) elif geom == 4 and len(coordinations) + atom.num_bonds == 1: away, d, natom = find_rotamer_nearest( at_pos, idatm_type[atom], atom, (list(atom.neighbors) + coordinations)[0], 3.5) else: away, d, natom = find_nearest(at_pos, atom, exclude, 3.5) bonded_pos = [] for bonded in atom.neighbors: bonded_pos.append(bonded._addh_coord) if coordinations: toward = coordinations[0]._addh_coord away2 = away away = None else: toward = None from chimerax.atomic.bond_geom import bond_positions from chimerax.geometry import distance_squared positions = bond_positions(at_pos, geom, bond_with_H_length(atom, geom), bonded_pos, toward=toward, coplanar=planar, away=away, away2=away2) if coordinations: coord_pos = None for pos in positions: d = distance_squared(pos, toward) if coord_pos is None or d < lowest: coord_pos = pos lowest = d positions.remove(coord_pos) if len(positions) > needed: positions = roomiest(positions, atom, 3.5, bonding_info)[:needed] alt_loc_info.append((alt_loc, occupancy, positions)) # delay adding Hs until all positions computed so that neighbors, etc. correct # for later alt locs add_altloc_hyds(atom, alt_loc_info, invert, bonding_info, total_hydrogens, naming_schema)
def make_ladder(nd, residues, params): """generate links between residues that are hydrogen bonded together""" # returns set of residues whose bases are drawn as rungs and # and have their atoms hidden all_shapes = [] # Create list of atoms from residues for donors and acceptors mol = residues[0].structure # make a set for quick inclusion test residue_set = set(residues) pbg = mol.pseudobond_group(mol.PBG_HYDROGEN_BONDS, create_type=None) if not pbg: bonds = () else: bonds = (p.atoms for p in pbg.pseudobonds) # only make one rung between residues even if there is more than one # h-bond depict_bonds = {} for a0, a1 in bonds: r0 = a0.residue r1 = a1.residue if r0 not in residue_set or r1 not in residue_set: continue non_base = (BackboneRiboseRE.match(a0.name), BackboneRiboseRE.match(a1.name)) if params.skip_nonbase_Hbonds and any(non_base): continue if r0.connects_to(r1): # skip covalently bonded residues continue if r1 < r0: r0, r1 = r1, r0 non_base = (non_base[1], non_base[0]) c3p0 = _c3pos(r0) if not c3p0: continue c3p1 = _c3pos(r1) if not c3p1: continue if params.rung_radius and not any(non_base): radius = params.rung_radius # elif r0.ribbon_display and r1.ribbon_display: # mgr = mol.ribbon_xs_mgr # radius = min(mgr.scale_nucleic) else: # TODO: radius = a0.structure.stickScale \ # * chimera.Molecule.DefaultBondRadius radius = a0.structure.bond_radius key = (r0, r1) if key in depict_bonds: prev_radius = depict_bonds[key][2] if prev_radius >= radius: continue depict_bonds[key] = (c3p0, c3p1, radius, non_base) matched_residues = set() if not params.stubs_only: for (r0, r1), (c3p0, c3p1, radius, non_base) in depict_bonds.items(): a0 = r0.find_atom("C2") a1 = r1.find_atom("C2") r0color = r0.ring_color r1color = r1.ring_color # choose mid-point to make purine larger try: is_purine0 = standard_bases[nucleic3to1( r0.name)]['tag'] == PURINE is_purine1 = standard_bases[nucleic3to1( r1.name)]['tag'] == PURINE except KeyError: is_purine0 = False is_purine1 = False if any(non_base) or is_purine0 == is_purine1: mid = 0.5 elif is_purine0: mid = purine_pyrimidine_ratio else: mid = 1.0 - purine_pyrimidine_ratio midpt = c3p0[1] + mid * (c3p1[1] - c3p0[1]) va, na, ta = get_cylinder(radius, c3p0[1], midpt, top=False) all_shapes.append( AtomicShapeInfo(va, na, ta, r0color, r0.atoms, str(r0))) va, na, ta = get_cylinder(radius, c3p1[1], midpt, top=False) all_shapes.append( AtomicShapeInfo(va, na, ta, r1color, r1.atoms, str(r1))) if not non_base[0]: matched_residues.add(r0) if not non_base[1]: matched_residues.add(r1) if not params.show_stubs: if params.hide: return all_shapes, matched_residues return all_shapes, () # draw stubs for unmatched nucleotide residues for r in residues: if r in matched_residues: continue c3p = _c3pos(r) if not c3p: continue ep0 = c3p[1] a = r.find_atom("C2") color = r.ring_color ep1 = None name = nucleic3to1(r.name) if name not in standard_bases: continue is_purine = standard_bases[name]['tag'] == PURINE if is_purine: a = r.find_atom('N1') if a: ep1 = a.coord else: # pyrimidine a = r.find_atom('N3') if a: ep1 = a.coord if ep1 is None: # find farthest atom from C3' dist_atom = (0, None) for a in r.atoms: dist = distance_squared(ep0, a.coord) if dist > dist_atom[0]: dist_atom = (dist, a) ep1 = dist_atom[1].coord va, na, ta = get_cylinder(params.rung_radius, ep0, ep1) all_shapes.append(AtomicShapeInfo(va, na, ta, color, r.atoms, str(r))) # make exposed end rounded (TODO: use a hemisphere) va, na, ta = get_sphere(params.rung_radius, ep1) all_shapes.append(AtomicShapeInfo(va, na, ta, color, r.atoms, str(r))) matched_residues.add(r) if params.hide: return all_shapes, matched_residues return all_shapes, ()