def set_size(self, axis_length, axis_radius): from chimerax.surface.shapes import cylinder_geometry, cone_geometry vaz, naz, taz = cylinder_geometry(radius=axis_radius, height=axis_length) vcz, ncz, tcz = cone_geometry(radius=axis_radius * 2, height=axis_length * 0.2, caps=True) from chimerax.geometry import Place vct = Place(origin=(0, 0, axis_length / 2)) vcz = vct.transform_points(vcz) nv = len(vaz) tcz = tcz + nv from numpy import concatenate vaz = concatenate((vaz, vcz)) naz = concatenate((naz, ncz)) taz = concatenate((taz, tcz)) nv = len(vaz) tx = Place(axes=[[0, 0, 1], [0, -1, 0], [1, 0, 0]]) vax, nax, tax = tx.transform_points(vaz), tx.transform_vectors( naz), taz.copy() + nv ty = Place(axes=[[1, 0, 0], [0, 0, -1], [0, 1, 0]]) vay, nay, tay = ty.transform_points(vaz), ty.transform_vectors( naz), taz.copy() + 2 * nv vc = self.vertex_colors self.set_geometry(concatenate((vax, vay, vaz)), concatenate((nax, nay, naz)), concatenate((tax, tay, taz))) self.vertex_colors = vc # Setting geometry clears vertex colors
def ellipsoid_geometry(center, axes, axis_lengths, num_triangles = 1000): from chimerax.surface import sphere_geometry varray, narray, tarray = sphere_geometry(num_triangles) narray = narray.copy() # Is same as varray for sphere. from chimerax.geometry import Place, scale, normalize_vectors ptf = Place(axes = axes, origin = center) * scale(axis_lengths) ptf.transform_points(varray, in_place = True) ntf = Place(axes = axes) * scale([1/l for l in axis_lengths]) ntf.transform_vectors(narray, in_place = True) normalize_vectors(narray) return varray, narray, tarray
def _show_surface(session, varray, tarray, color, mesh, center, rotation, qrotation, coordinate_system, slab, model_id, shape_name, edge_mask = None, sharp_slab = False): if center is not None or rotation is not None or qrotation is not None: from chimerax.geometry import Place, translation tf = Place() if rotation is not None: tf = rotation * tf if qrotation is not None: tf = qrotation * tf if center is not None: from chimerax.core.commands import Center if isinstance(center, Center): center = center.scene_coordinates() tf = translation(center) * tf varray = tf.transform_points(varray) from chimerax.surface import calculate_vertex_normals narray = calculate_vertex_normals(varray, tarray) if slab is not None: from chimerax.mask.depthmask import slab_surface varray, narray, tarray = slab_surface(varray, tarray, narray, slab, sharp_edges = sharp_slab) s = _surface_model(session, model_id, shape_name, coordinate_system) s.set_geometry(varray, narray, tarray) if color is not None: s.color = color if mesh: s.display_style = s.Mesh if edge_mask is not None: s.edge_mask = edge_mask # Hide spokes of hexagons. _add_surface(s) return s
def mesh_geometry(mesh, seg): # TODO: SFF format data structure mix vertices and normals, calling both vertices -- a nightmare. # Semantics of which normal belong with which vertices unclear (consecutive in polygon?). # Efficiency reading is horrible. Ask Paul K to make separate vertex and normal lists. nv = mesh.num_vertices // 2 from numpy import empty, float32 va = empty((nv, 3), float32) na = empty((nv, 3), float32) for i, v in enumerate(mesh.vertices): vid = v.id if vid != i: raise ValueError( 'Require mesh vertices be numbers consecutively from 0, got vertex id %d in position %d' % (vid, i)) d = v.designation # 'surface' or 'normal' if d == 'surface': if vid % 2 == 1: raise ValueError( 'Require odd mesh indices to be normals, got a vertex at position %d' % vid) va[vid // 2] = v.point elif d == 'normal': if vid % 2 == 0: raise ValueError( 'Require even mesh indices to be vertices, got a normal at position %d' % vid) na[vid // 2] = v.point else: raise ValueError( 'Vertex %d designation "%s" is not "surface" or "normal"' % (v.id, d)) ''' vids = list(set(v.id for v in mesh.vertices if v.designation == 'surface')) vids.sort() print ('vertex ids', vids[:3], 'num', len(vids), 'last', vids[-1]) ''' if mesh.transform_id is None: from chimerax.geometry import Place, scale # transform = scale((160,160,160)) * Place(seg.transforms[0].data_array) transform = Place(seg.transforms[0].data_array) * scale( (160, 160, 160)) else: transform = transform_by_id(seg, mesh.transform_id) transform.transform_points(va, in_place=True) transform.transform_normals(na, in_place=True) tri = [] for p in mesh.polygons: # print ('poly', len(p.vertex_ids), p.vertex_ids[:6]) t = tuple(vid // 2 for vid in p.vertices if vid % 2 == 0) if len(t) != 3: raise ValueError( 'Require polygons to be single triangles, got polygon with %d vertices' % len(t)) tri.append(t) ''' last_vid = None for vid in p.vertex_ids: if vid % 2 == 0: if last_vid is not None: # tri.append((vid//2,last_vid//2)) tri.append((vid//2,last_vid//2,vid//2)) last_vid = vid first_vid = p.vertex_ids[0] tri.append((last_vid//2,first_vid//2,last_vid//2)) ''' from numpy import array, int32 ta = array(tri, int32) return va, na, ta
def ses_surface_geometry(xyz, radii, probe_radius=1.4, grid_spacing=0.5, sas=False): ''' Calculate a solvent excluded molecular surface using a distance grid contouring method. Vertex, normal and triangle arrays are returned. If sas is true then the solvent accessible surface is returned instead. ''' # Compute bounding box for atoms xyz_min, xyz_max = xyz.min(axis=0), xyz.max(axis=0) pad = 2 * probe_radius + radii.max() + grid_spacing origin = [x - pad for x in xyz_min] # Create 3d grid for computing distance map from math import ceil s = grid_spacing shape = [ int(ceil((xyz_max[a] - xyz_min[a] + 2 * pad) / s)) for a in (2, 1, 0) ] # print('ses surface grid size', shape, 'spheres', len(xyz)) from numpy import empty, float32, sqrt try: matrix = empty(shape, float32) except (MemoryError, ValueError): raise MemoryError( 'Surface calculation out of memory trying to allocate a grid %d x %d x %d ' % (shape[2], shape[1], shape[0]) + 'to cover xyz bounds %.3g,%.3g,%.3g ' % tuple(xyz_min) + 'to %.3g,%.3g,%.3g ' % tuple(xyz_max) + 'with grid size %.3g' % grid_spacing) max_index_range = 2 matrix[:, :, :] = max_index_range # Transform centers and radii to grid index coordinates from chimerax.geometry import Place xyz_to_ijk_tf = Place( ((1.0 / s, 0, 0, -origin[0] / s), (0, 1.0 / s, 0, -origin[1] / s), (0, 0, 1.0 / s, -origin[2] / s))) from numpy import float32 ijk = xyz.astype(float32) xyz_to_ijk_tf.transform_points(ijk, in_place=True) ri = radii.astype(float32) ri += probe_radius ri /= s # Compute distance map from surface of spheres, positive outside. from chimerax.map import sphere_surface_distance sphere_surface_distance(ijk, ri, max_index_range, matrix) # Get the SAS surface as a contour surface of the distance map from chimerax.map import contour_surface level = 0 sas_va, sas_ta, sas_na = contour_surface(matrix, level, cap_faces=False, calculate_normals=True) if sas: xyz_to_ijk_tf.inverse().transform_points(sas_va, in_place=True) return sas_va, sas_na, sas_ta # Compute SES surface distance map using SAS surface vertex # points as probe sphere centers. matrix[:, :, :] = max_index_range rp = empty((len(sas_va), ), float32) rp[:] = float(probe_radius) / s sphere_surface_distance(sas_va, rp, max_index_range, matrix) ses_va, ses_ta, ses_na = contour_surface(matrix, level, cap_faces=False, calculate_normals=True) # Transform surface from grid index coordinates to atom coordinates xyz_to_ijk_tf.inverse().transform_points(ses_va, in_place=True) # Delete connected components more than 1.5 probe radius from atom spheres. kvi = [] kti = [] from ._surface import connected_pieces vtilist = connected_pieces(ses_ta) for vi, ti in vtilist: v0 = ses_va[vi[0], :] d = xyz - v0 d2 = (d * d).sum(axis=1) adist = (sqrt(d2) - radii).min() if adist < 1.5 * probe_radius: kvi.append(vi) kti.append(ti) from .split import reduce_geometry from numpy import concatenate keepv = concatenate(kvi) if kvi else [] keept = concatenate(kti) if kti else [] va, na, ta = reduce_geometry(ses_va, ses_na, ses_ta, keepv, keept) return va, na, ta
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