def _cut_triangles(edge_cuts, varray, narray, tarray, carray): e = {} vae = [] nae = [] cae = [] nv = len(varray) from chimerax.geometry import normalize_vector for v1, v2, f in edge_cuts: p = (1 - f) * varray[v1] + f * varray[v2] n = normalize_vector((1 - f) * narray[v1] + f * narray[v2]) vi = nv + len(vae) vae.extend((p, p)) nae.extend((n, n)) cae.extend((carray[v1], carray[v2])) e[(v1, v2)] = vi e[(v2, v1)] = vi + 1 if len(vae) == 0: return varray, narray, tarray, carray tae = [] for v1, v2, v3 in tarray: p12, p23, p31 = e.get((v1, v2)), e.get((v2, v3)), e.get((v3, v1)) cuts = 3 - [p12, p23, p31].count(None) p21, p32, p13 = e.get((v2, v1)), e.get((v3, v2)), e.get((v1, v3)) if cuts == 3: # Add triangle center point, 3 copies p = (vae[p12 - nv] + vae[p23 - nv] + vae[p31 - nv]) / 3 n = normalize_vector(nae[p12 - nv] + nae[p23 - nv] + nae[p31 - nv]) tc1 = nv + len(vae) tc2 = tc1 + 1 tc3 = tc1 + 2 vae.extend((p, p, p)) nae.extend((n, n, n)) cae.extend((carray[v1], carray[v2], carray[v3])) tae.extend(((v1, p12, tc1), (v1, tc1, p13), (v2, p23, tc2), (v2, tc2, p21), (v3, p31, tc3), (v3, tc3, p32))) elif cuts == 2: if p31 is None: tae.extend(((v1, p12, p32), (v1, p32, v3), (v2, p23, p21))) elif p12 is None: tae.extend(((v2, p23, p13), (v2, p13, v1), (v3, p31, p32))) elif p23 is None: tae.extend(((v3, p31, p21), (v3, p21, v2), (v1, p12, p13))) elif cuts == 1: raise ValueError('Triangle with one cut edge') elif cuts == 0: tae.append((v1, v2, v3)) from numpy import concatenate, array va = concatenate((varray, array(vae, varray.dtype))) na = concatenate((narray, array(nae, narray.dtype))) ta = array(tae, tarray.dtype) ca = concatenate((carray, array(cae, carray.dtype))) return va, na, ta, ca
def polygon_coordinate_frame(polygon): p = polygon c = p.center() za = p.normal() xa = p.vertices[0].coord - c from chimerax.geometry import normalize_vector, cross_product, Place ya = normalize_vector(cross_product(za, xa)) xa = normalize_vector(cross_product(ya, za)) tf = [(xa[a], ya[a], za[a], c[a]) for a in (0, 1, 2)] return Place(tf)
def edge_coordinate_frame(edge): x0, x1 = edge_alignment_points(edge) p = edge.polygon c = p.center() c01 = 0.5 * (x0 + x1) from chimerax.geometry import cross_product, normalize_vector, Place xa = normalize_vector(x1 - x0) za = normalize_vector(cross_product(xa, c01 - c)) ya = cross_product(za, xa) tf = Place(((xa[0], ya[0], za[0], c01[0]), (xa[1], ya[1], za[1], c01[1]), (xa[2], ya[2], za[2], c01[2]))) return tf
def surface_path(session, surface, length=100, rgba=(255, 255, 0, 255), radius=1, geodesic=True): ep = edge_pairs(surface.triangles) from random import choice e = choice(tuple(ep.keys())) # e = first_element(ep.keys()) vertices = surface.vertices if geodesic: tnormals = edge_triangle_normals(surface.vertices, surface.triangles) # Start in direction perpendicular to starting edge. eo = (e[1], e[0]) ev = edge_vector(eo, vertices) from chimerax.geometry import cross_product, normalize_vector direction = normalize_vector(cross_product(tnormals[eo], ev)) else: direction, tnormals = None points = make_surface_path(e, ep, length, vertices, direction, tnormals) from chimerax.markers import MarkerSet, create_link ms = MarkerSet(session, 'surface path') mprev = None for id, xyz in enumerate(points): m = ms.create_marker(xyz, rgba, radius, id) if mprev is not None: create_link(mprev, m, rgba=rgba, radius=radius) mprev = m session.models.add([ms])
def next_direction(direction, e, vertices, normal1, normal2): ev = edge_vector(e, vertices) from chimerax.geometry import cross_product, inner_product, normalize_vector en1, en2 = cross_product(ev, normal1), cross_product(ev, normal2) d = inner_product(direction, ev) * ev + inner_product(direction, en1) * en2 d = normalize_vector(d) return d
def interpolate_corkscrew(xf, c0, c1, minimum_rotation = 0.1): ''' Rotate and move along a circular arc perpendicular to the rotation axis and translate parallel the rotation axis. This makes the initial geometric center c0 traverse a helical path to the final center c1. The circular arc spans an angle equal to the rotation angle so it is nearly a straight segment for small angles, and for the largest possible rotation angle of 180 degrees it is a half circle centered half-way between c0 and c1 in the plane perpendicular to the rotation axis. Rotations less than the minimum (degrees) are treated as no rotation. ''' from chimerax.geometry import normalize_vector dc = c1 - c0 axis, angle = xf.rotation_axis_and_angle() # a is in degrees. if abs(angle) < minimum_rotation: # Use linear instead of corkscrew interpolation to # avoid numerical precision problems at small rotation angles. # ChimeraX bug #2928. center = c0 shift = dc else: from chimerax.geometry import inner_product, cross_product, norm tra = inner_product(dc, axis) # Magnitude of translation along rotation axis. shift = tra*axis vt = dc - tra * axis # Translation perpendicular to rotation axis. v0 = cross_product(axis, vt) if norm(v0) == 0 or angle == 0: center = c0 else: import math l = 0.5*norm(vt) / math.tan(math.radians(angle / 2)) center = c0 + 0.5*vt + l*normalize_vector(v0) return axis, angle, center, shift
def __init__(self, axis=(0, 1, 0), center=(0, 0, 0), angle=90, rock=None, wobble=None, wobble_aspect=0.3, wobble_axis=None, camera=None, models=None, atoms=None): self._axis = axis self._center = center self._full_angle = angle self._rock = rock self._wobble = wobble if wobble is not None and wobble_axis is None: from chimerax.geometry import cross_product, normalize_vector wobble_axis = normalize_vector( cross_product(camera.view_direction(), axis)) self._wobble_axis = wobble_axis self._wobble_aspect = wobble_aspect self._wobble_axis = wobble_axis self._camera = camera self._models = models self._atoms = atoms
def extrusion_transforms(path, tangents, yaxis=None): from chimerax.geometry import identity, vector_rotation, translation tflist = [] if yaxis is None: # Make xy planes for coordinate frames at each path point not rotate # from one point to next. tf = identity() n0 = (0, 0, 1) for p1, n1 in zip(path, tangents): tf = vector_rotation(n0, n1) * tf tflist.append(translation(p1) * tf) n0 = n1 else: # Make y-axis of coordinate frames at each point align with yaxis. from chimerax.geometry import normalize_vector, cross_product, Place for p, t in zip(path, tangents): za = t xa = normalize_vector(cross_product(yaxis, za)) ya = cross_product(za, xa) tf = Place( ((xa[0], ya[0], za[0], p[0]), (xa[1], ya[1], za[1], p[1]), (xa[2], ya[2], za[2], p[2]))) tflist.append(tf) return tflist
def random_direction(): z = (1,1,1) from chimerax.geometry import norm, normalize_vector from random import random while norm(z) > 1: z = (1-2*random(), 1-2*random(), 1-2*random()) return normalize_vector(z)
def edge_triangle_normals(vertices, triangles): tn = {} from chimerax.geometry import cross_product, normalize_vector for v1, v2, v3 in triangles: x1, x2, x3 = vertices[v1], vertices[v2], vertices[v3] n = normalize_vector(cross_product(x2 - x1, x3 - x1)) tn[(v1, v2)] = tn[(v2, v3)] = tn[(v3, v1)] = n return tn
def path_tangents(points): # TODO: Handle coincident points. from chimerax.geometry import linear_combination, normalize_vector tang = [linear_combination(1, points[1], -1, points[0])] for i in range(1, len(points) - 1): tang.append(linear_combination(1, points[i + 1], -1, points[i - 1])) tang.append(linear_combination(1, points[-1], -1, points[-2])) ntang = [normalize_vector(t) for t in tang] return ntang
def curve_segment_placement(curve, t0, t1): ''' Create a Place instance mapping segment (0,0,0), (0,0,length) to curve points at parameter value t0 and t1. ''' from chimerax.geometry import normalize_vector, cross_product, Place if t1 > t0: xyz0, xyz1 = curve.position(t0), curve.position(t1) x_axis = normalize_vector(xyz1 - xyz0) center = xyz0 else: x_axis = normalize_vector(curve.velocity(t0)) center = curve.position(t0) y_axis = normalize_vector(curve.normal( 0.5 * (t0 + t1))) # May not be normal to x_axis z_axis = normalize_vector(cross_product(x_axis, y_axis)) y_axis = normalize_vector(cross_product(z_axis, x_axis)) p = Place(axes=(x_axis, y_axis, z_axis), origin=center) return p
def _box_geometry(corners): '''Use separate vertices and normals for each face so edges are sharp.''' from chimerax.geometry import normalize_vector nx = normalize_vector(corners[1] - corners[0]) ny = normalize_vector(corners[3] - corners[0]) nz = normalize_vector(corners[4] - corners[0]) from numpy import concatenate, array, int32, float32 varray = concatenate((corners, corners, corners)).astype(float32) narray = varray.copy() narray[:] = (-nz, -nz, -nz, -nz, nz, nz, nz, nz, -ny, -ny, ny, ny, -ny, -ny, ny, ny, -nx, nx, nx, -nx, -nx, nx, nx, -nx) bottom_top = [(0, 2, 1), (0, 3, 2), (4, 5, 6), (4, 6, 7)] back_front = [(8 + v0, 8 + v1, 8 + v2) for v0, v1, v2 in ((2, 3, 7), (2, 7, 6), (0, 1, 5), (0, 5, 4))] left_right = [(16 + v0, 16 + v1, 16 + v2) for v0, v1, v2 in ((3, 0, 4), (3, 4, 7), (1, 2, 6), (1, 6, 5))] triangles = bottom_top + back_front + left_right tarray = array(triangles, int32) return varray, narray, tarray
def perspective_direction(window_x, window_y, window_size, field_of_view): ''' Return points in camera coordinates at a given window pixel position at specified z depths. Field of view is in degrees. ''' from math import radians, tan fov = radians(field_of_view) t = tan(0.5 * fov) # Field of view is in width wp, hp = window_size # Screen size in pixels wx, wy = 2 * (window_x - 0.5 * wp) / wp, 2 * (0.5 * hp - window_y) / wp from chimerax.geometry import normalize_vector d = normalize_vector((t * wx, t * wy, -1)) return d
def explode_contact(self, distance = 30, move_group = None): g1, g2 = (self.group1, self.group2) xyz1, xyz2 = [self.contact_residue_atoms(g).scene_coords.mean(axis = 0) for g in (g1,g2)] from chimerax.geometry import normalize_vector step = (0.5*distance)*normalize_vector(xyz2 - xyz1) if move_group is g1: g1.move(-2*step) g2.unmove() elif move_group is g2: g1.unmove() g2.move(2*step) else: g1.move(-step) g2.move(step)
def _restricted_shift(self, shift): '''Return shift resticted to be in a plane.''' raxis = self._restrict_to_plane models = self.models() if models is None: scene_axis = raxis else: scene_axis = models[0].position.transform_vector(raxis) axis = self.camera_position.inverse().transform_vector( scene_axis) # Camera coords from chimerax.geometry import normalize_vector, inner_product axis = normalize_vector(axis) rshift = -inner_product(axis, shift) * axis + shift return rshift
def unroll_operation(v, r0, r1, h, center, axis, gsp, subregion, step, modelId): from math import ceil, pi zsize = int(max(1, ceil(h / gsp))) # cylinder height xsize = int(max(1, ceil((r1 - r0) / gsp))) # slab thickness rmid = 0.5 * (r0 + r1) circum = rmid * 2 * pi ysize = int(max(1, ceil(circum / gsp))) # circumference from chimerax.geometry import normalize_vector axis = normalize_vector(axis) agrid_points = annulus_grid(r0, r1, center, axis, ysize, xsize) grid_points = agrid_points.reshape((ysize * xsize, 3)) grid_points[:] += tuple([-0.5 * h * ai for ai in axis]) # Shift annulus. from numpy import empty values = empty((zsize, ysize, xsize), v.data.value_type) axis_step = tuple([h * float(ai) / (zsize - 1) for ai in axis]) for i in range(zsize): vval = v.interpolated_values(grid_points, subregion=subregion, step=step) values[i, :, :] = vval.reshape((ysize, xsize)) grid_points[:] += axis_step # Shift annulus. from chimerax.map_data import ArrayGridData gstep = (float(r1 - r0) / (xsize - 1), circum / (ysize - 1), float(h) / (zsize - 1)) gorigin = (center[0] + r0, center[1] - 0.5 * circum, center[2] - 0.5 * h) g = ArrayGridData(values, gorigin, gstep, name='unrolled %s' % v.name) from chimerax.map import volume_from_grid_data vu = volume_from_grid_data(g, v.session, model_id=modelId) vu.copy_settings_from(v, copy_region=False, copy_active=False, copy_colors=False) if axis[0] != 0 or axis[1] != 0: # Rotate so unrolled volume is tangential to cylinder from chimerax.geometry import orthonormal_frame vu.position = v.position * orthonormal_frame(axis) return vu
def test_phi(dp, ap, bp, phi_plane, phi): if phi_plane: normal = normalize_vector( cross_product(phi_plane[1] - phi_plane[0], phi_plane[2] - phi_plane[1])) D = dot(normal, phi_plane[1]) bproj = project(bp, normal, D) aproj = project(ap, normal, D) dproj = project(dp, normal, D) ang = angle(bproj, aproj, dproj) if ang < phi: if hbond.verbose: print("phi criteria failed (%g < %g)" % (ang, phi)) return False if hbond.verbose: print("phi criteria OK (%g >= %g)" % (ang, phi)) else: if hbond.verbose: print("phi criteria irrelevant") return True
def axis(self): moving, fixed = self.moving_side.coord, self.bond.other_atom( self.moving_side).coord from chimerax.geometry import normalize_vector axis = normalize_vector(moving - fixed) return axis
if pyrimidine_max[2] < max[2]: pyrimidine_max[2] = max[2] pu = (purine_max[1] - purine_min[1]) py = (pyrimidine_max[1] - pyrimidine_min[1]) purine_pyrimidine_ratio = pu / (pu + py) del b, coord, min, max, pu, py # precompute z-plane rotation correction factor z_axis = numpy.array((0, 0, 1.)) for b in standard_bases.values(): pts = [b["atoms"][n] for n in b["ring atom names"][0:2]] y_axis = pts[0] - pts[1] # insure that y_axis is perpendicular to z_axis # (should be zero already) y_axis[2] = 0.0 normalize_vector(y_axis) x_axis = numpy.cross(y_axis, z_axis) xf = Place(matrix=((x_axis[0], y_axis[0], z_axis[0], 0.0), (x_axis[1], y_axis[1], z_axis[1], 0.0), (x_axis[2], y_axis[2], z_axis[2], 0.0)) # TODO: orthogonalize=True ) # axis, angle = xf.getRotation() # print("axis = %s, angle = %s" % (axis, angle)) b["correction factor"] = xf.inverse() del b, pts, x_axis, y_axis, z_axis, xf system_dimensions = { # predefined dimensions in local coordinate frame # note: (0, 0) corresponds to position of C1' 'small': {
def triangle_normal(v0,v1,v2): e10, e20 = v1 - v0, v2 - v0 from chimerax.geometry import normalize_vector, cross_product n = normalize_vector(cross_product(e10, e20)) return n
def draw_slab(nd, residue, name, params): shapes = [] standard = standard_bases[name] ring_atom_names = standard["ring atom names"] atoms = get_ring(residue, ring_atom_names) if not atoms: return shapes plane = Plane([a.coord for a in atoms]) info = find_dimensions(params.dimensions) tag = standard['tag'] slab_corners = info[tag] origin = residue.find_atom(anchor(info[ANCHOR], tag)).coord origin = plane.nearest(origin) pts = [plane.nearest(a.coord) for a in atoms[0:2]] y_axis = pts[0] - pts[1] normalize_vector(y_axis) x_axis = numpy.cross(y_axis, plane.normal) xf = Place(matrix=((x_axis[0], y_axis[0], plane.normal[0], origin[0]), (x_axis[1], y_axis[1], plane.normal[1], origin[1]), (x_axis[2], y_axis[2], plane.normal[2], origin[2]))) xf = xf * standard["correction factor"] color = residue.ring_color half_thickness = params.thickness / 2 llx, lly = slab_corners[0] llz = -half_thickness urx, ury = slab_corners[1] urz = half_thickness center = (llx + urx) / 2, (lly + ury) / 2, 0 if params.shape == 'box': llb = (llx, lly, llz) urf = (urx, ury, urz) xf2 = xf va, na, ta = box_geometry(llb, urf) pure_rotation = True elif params.shape == 'muffler': radius = (urx - llx) / 2 * _SQRT2 xf2 = xf * translation(center) xf2 = xf2 * scale((1, 1, half_thickness * _SQRT2 / radius)) height = ury - lly va, na, ta = get_cylinder(radius, numpy.array((0, -height / 2, 0)), numpy.array((0, height / 2, 0))) pure_rotation = False elif params.shape == 'ellipsoid': # need to reach anchor atom xf2 = xf * translation(center) sr = (ury - lly) / 2 * _SQRT3 xf2 = xf2 * scale( ((urx - llx) / 2 * _SQRT3 / sr, 1, half_thickness * _SQRT3 / sr)) va, na, ta = get_sphere(sr, (0, 0, 0)) pure_rotation = False else: raise RuntimeError('unknown base shape') description = '%s %s' % (residue, tag) xf2.transform_points(va, in_place=True) xf2.transform_normals(na, in_place=True, is_rotation=pure_rotation) shapes.append(AtomicShapeInfo(va, na, ta, color, atoms, description)) if not params.orient: return shapes # show slab orientation by putting "bumps" on surface if tag == PYRIMIDINE: center = (llx + urx) / 2.0, (lly + ury) / 2, half_thickness va, na, ta = get_sphere(half_thickness, center) xf.transform_points(va, in_place=True) xf.transform_normals(na, in_place=True, is_rotation=True) shapes.append(AtomicShapeInfo(va, na, ta, color, atoms, description)) else: # purine center = (llx + urx) / 2.0, lly + (ury - lly) / 3, half_thickness va, na, ta = get_sphere(half_thickness, center) xf.transform_points(va, in_place=True) xf.transform_normals(na, in_place=True, is_rotation=True) shapes.append(AtomicShapeInfo(va, na, ta, color, atoms, description)) center = (llx + urx) / 2.0, lly + (ury - lly) * 2 / 3, half_thickness va, na, ta = get_sphere(half_thickness, center) xf.transform_points(va, in_place=True) xf.transform_normals(na, in_place=True, is_rotation=True) shapes.append(AtomicShapeInfo(va, na, ta, color, atoms, description)) return shapes
def normal(self): x0, x1, x2 = [m.coord for m in self.vertices[:3]] from chimerax.geometry import normalize_vector, cross_product n = normalize_vector(cross_product(x1 - x0, x2 - x1)) return n
def test_tau(tau, tau_sym, don_acc, dap, op): if tau is None: if hbond.verbose: print("tau test irrelevant") return True # sulfonamides and phosphonamides can have bonded NH2 groups that are planar enough # to be declared Npl, so use the hydrogen positions to determine planarity if possible if tau_sym == 4: bonded_pos = hyd_positions(don_acc) else: # since we expect tetrahedral hydrogens to be oppositely aligned from the attached # tetrahedral center, we can't use their positions for tau testing bonded_pos = [] heavys = [a for a in don_acc.neighbors if a.element.number > 1] if 2 * len(bonded_pos) != tau_sym: bonded_pos = hyd_positions(heavys[0], include_lone_pairs=True) for b in heavys[0].neighbors: if b == don_acc or b.element.number < 2: continue bonded_pos.append(b._hb_coord) if not bonded_pos: if hbond.verbose: print("tau indeterminate; default okay") return True if 2 * len(bonded_pos) != tau_sym: raise AtomTypeError( "Unexpected tau symmetry (%d, should be %d) for donor/acceptor %s" % (2 * len(bonded_pos), tau_sym, don_acc)) normal = normalize_vector(heavys[0]._hb_coord - dap) if tau < 0.0: test = lambda ang, t=tau: ang <= 0.0 - t else: test = lambda ang, t=tau: ang >= t proj_other_pos = project(op, normal, 0.0) proj_da_pos = project(dap, normal, 0.0) for bpos in bonded_pos: proj_bpos = project(bpos, normal, 0.0) ang = angle(proj_other_pos, proj_da_pos, proj_bpos) if test(ang): if tau < 0.0: if hbond.verbose: print("tau okay (%g < %g)" % (ang, -tau)) return True else: if tau > 0.0: if hbond.verbose: print("tau too small (%g < %g)" % (ang, tau)) return False if tau < 0.0: if hbond.verbose: print("all taus too big (> %g)" % -tau) return False if hbond.verbose: print("all taus acceptable (> %g)" % tau) return True
def new_color(self): from random import random as r from chimerax.geometry import normalize_vector rgba = tuple(normalize_vector((r(), r(), r()))) + (1, ) self._blob_color.color = rgba