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 curve_segment_end(curve, t, length, tolerance=1e-3, max_steps=100): ''' Compute a point on the curve beyond curve parameter value t which is a distance length from the point at position t. Can return None if convergence fails. This iteratively takes linear steps based on the curve velocity vector to find the desired end point. ''' if length == 0: return t xyz0 = curve.position(t) v = curve.velocity(t) frac = 0.5 from chimerax.geometry import norm, inner_product t1 = t + frac * length / norm(v) for step in range(max_steps): xyz1 = curve.position(t1) d = norm(xyz1 - xyz0) if abs(d - length) < tolerance * length: return t1 v1 = curve.velocity(t1) # Want |xyz1 + v1*dt - xyz0| = length delta = xyz1 - xyz0 a, b, c = (inner_product(v1, v1), 2 * inner_product(v1, delta), inner_product(delta, delta) - length * length) dt1, dt2 = quadratic_roots(a, b, c) if dt1 is None: # No point in tangent line is within target length. # Go to closest approach dt1 = dt2 = -b / 2 * a dt_min = dt1 if abs(dt1) < abs(dt2) else dt2 t1 += frac * dt_min return None
def rotation_step(points, point_weights, center, data_array, xyz_to_ijk_transform, ijk_step_size, metric, syminv=[], values=None, gradients=None): axis = torque_axis(points, point_weights, center, data_array, xyz_to_ijk_transform, metric, syminv=syminv, values=values, gradients=gradients) from chimerax.geometry import norm, place na = norm(axis) if len(points) == 1 or na == 0: axis = (0, 0, 1) angle = 0 else: axis /= na angle = angle_step(axis, points, center, xyz_to_ijk_transform, ijk_step_size) move_tf = place.rotation(axis, angle, center) return move_tf
def translation_step(points, point_weights, center, data_array, xyz_to_ijk_transform, ijk_step_size, metric, syminv=[], values=None, gradients=None): g = gradient_direction(points, point_weights, data_array, xyz_to_ijk_transform, metric, syminv=syminv, values=values, gradients=gradients) from numpy import array, float, dot as matrix_multiply gijk = matrix_multiply(xyz_to_ijk_transform.matrix[:, :3], g) from chimerax.geometry import norm, place n = norm(gijk) if n > 0: delta = g * (ijk_step_size / n) else: delta = array((0, 0, 0), float) delta_tf = place.translation(delta) return delta_tf
def angle_pos(atom_pos, bond_pos, bond_length, degrees, coplanar=None): if coplanar is not None and len(coplanar) > 0: # may have one or two coplanar positions specified, # if two, compute both resultant positions and average # (the up vector has to be negated for the second one) xforms = [] if len(coplanar) > 2: raise ValueError("More than 2 coplanar positions specified!") for cpos in coplanar: up = cpos - atom_pos if xforms: up = tinyarray.negative(up) # lookAt puts ref point opposite that of zAlign, so # also rotate 180 degrees around y axis xform = rotation( (0.0, 1.0, 0.0), 180.0) * look_at(atom_pos, bond_pos, up) xforms.append(xform) else: xforms = [z_align(atom_pos, bond_pos)] points = [] for xform in xforms: radians = pi * degrees / 180.0 angle = tinyarray.array( [0.0, bond_length * sin(radians), bond_length * cos(radians)]) points.append(xform.inverse() * angle) if len(points) > 1: midpoint = points[0] + (points[1] - points[0]) / 2.0 v = midpoint - atom_pos v = v * bond_length / norm(v) return atom_pos + v return tinyarray.array(points[0])
def mouse_drag(self, event): v = self._map if v is None or self._xy_last is None: self.mouse_down(event) return xl, yl = self._xy_last x, y = event.position() dx, dy = (x - xl, yl - y) if dx == 0 and dy == 0: return camera = self.session.main_view.camera if event.shift_down(): # Translate slab ro = v.rendering_options spacing = ro.tilted_slab_spacing slab_normal = v.scene_position.transform_vector( ro.tilted_slab_axis) move_dir = camera.position.transform_vector((dx, dy, 0)) from chimerax.geometry import inner_product, norm sign = 1 if inner_product(move_dir, slab_normal) > 0 else -1 dist = sign * norm(move_dir) * spacing self._move_slab(dist) else: # Rotate slab from math import sqrt dn = sqrt(dx * dx + dy * dy) rangle = dn raxis = camera.position.transform_vector((-dy / dn, dx / dn, 0)) self._rotate_slab(raxis, rangle) self._xy_last = (x, y)
def _line_orientation(from_point, to_point, axis, height): if from_point is None and to_point is None and axis is None: return None c,h,r = None, height, None from chimerax.core.commands import Axis, Center if isinstance(axis, Axis): axis = axis.scene_coordinates() if isinstance(from_point, Center): from_point = from_point.scene_coordinates() if isinstance(to_point, Center): to_point = to_point.scene_coordinates() from chimerax.geometry import vector_rotation, norm if axis is not None: r = vector_rotation((0,0,1), axis) else: from numpy import array, float32 axis = array((0,0,1), float32) if from_point is not None and to_point is not None: c = 0.5 * (from_point + to_point) v = to_point - from_point r = vector_rotation((0,0,1), v) h = norm(v) elif from_point is not None and to_point is None: c = from_point + 0.5*height*axis elif from_point is None and to_point is not None: c = to_point - 0.5*height*axis return h, c, r
def buried_sphere_area(i, centers, radii, draw=False): if draw: surf0 = sphere_model([i], centers, radii) from chimerax.geometry import norm jlist = [ j for j in range(len(centers)) if j != i and norm(centers[j] - centers[i]) < radii[j] + radii[i] ] # jlist = list(range(i)) + list(range(i+1,len(centers))) print(len(jlist), 'spheres intersect sphere', i) surfn = sphere_model(jlist, centers, radii) for p in surfn.child_drawings(): p.color = (.7, .7, .9, .7) draw.add_models((surf0, surfn)) r = radii[i] # Check if sphere is completely contained in another sphere if sphere_in_another_sphere(i, centers, radii): area = 4 * pi * r * r return area # Compute sphere intersections circles = sphere_intersection_circles(i, centers, radii) # Compute analytical buried area on sphere. area = area_in_circles_on_unit_sphere(circles, draw, centers[i], r) * r * r return area
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 line_position(xyz, line): xyz1, xyz2 = line dxyz = xyz - xyz1 xyz12 = xyz2 - xyz1 from chimerax.geometry import norm, inner_product d2 = norm(xyz12) f = inner_product(dxyz, xyz12) / d2 return f
def _center_point_matching_depth(self, point): cam_pos = self.camera.position.origin() vd = self.camera.view_direction() hyp = point - cam_pos from chimerax.geometry import inner_product, norm distance = inner_product(hyp, vd) cr = cam_pos + distance * vd old_cofr = self._center_of_rotation if norm(cr - old_cofr) < 1e-6 * distance: # Avoid jitter if camera has not moved cr = old_cofr return cr
def cut_distances(va, nc=4): from chimerax.geometry import norm cuts = [] cut = [0] * nc n = len(va) // (4 * nc) for s in range(n - 1): for c in range(nc): e = va[c * n:] if s % 2 == 0 else va[((c + nc // 2) % nc) * n:] cut[c] += norm(e[s + 1] - e[s]) cuts.append(tuple(cut)) return cuts
def tetra_pos(bondee, bonded, bond_len, toward=None, away=None, toward2=None, away2=None): new_bonded = [] cur_bonded = bonded[:] if len(cur_bonded) == 0: pos = single_pos(bondee, bond_len, toward, away) toward = toward2 away = away2 new_bonded.append(pos) cur_bonded.append(pos) if len(cur_bonded) == 1: # add at 109.5 degree angle coplanar = toward or away if coplanar: coplanar = [coplanar] else: coplanar = None pos = angle_pos(bondee, cur_bonded[0], bond_len, 109.5, coplanar=coplanar) if toward or away: # find the other 109.5 position in the toward/away # plane and the closer/farther position as appropriate old = normalize(bondee - cur_bonded[0]) new = pos - bondee midpoint = tinyarray.array(bondee + old * norm(new) * cos705) other_pos = pos + (midpoint - pos) * 2 d1 = sqlength(pos - (toward or away)) d2 = sqlength(other_pos - (toward or away)) if toward is not None: if d2 < d1: pos = other_pos elif away is not None and d2 > d1: pos = other_pos new_bonded.append(pos) cur_bonded.append(pos) if len(cur_bonded) == 2: # add along anti-bisector of current bonds and raised up # 54.75 degrees from plane of those bonds (half of 109.5) v1 = normalize(cur_bonded[0] - bondee) v2 = normalize(cur_bonded[1] - bondee) anti_bi = normalize(tinyarray.negative(v1 + v2)) # in order to stabilize the third and fourth tetrahedral # positions, cross the longer vector by the shorter if sqlength(v1) > sqlength(v2): cross_v = normalize(cross_product(v1, v2)) else: cross_v = normalize(cross_product(v2, v1)) anti_bi = anti_bi * cos5475 * bond_len cross_v = cross_v * sin5475 * bond_len pos = bondee + anti_bi + cross_v if toward or away: other_pos = bondee + anti_bi - cross_v d1 = sqlength(pos - (toward or away)) d2 = sqlength(other_pos - (toward or away)) if toward is not None: if d2 < d1: pos = other_pos elif away is not None and d2 > d1: pos = other_pos new_bonded.append(pos) cur_bonded.append(pos) if len(cur_bonded) == 3: unitized = [] for cb in cur_bonded: v = normalize(cb - bondee) unitized.append(bondee + v) pl = Plane(unitized) normal = pl.normal # if normal on other side of plane from bondee, we need to # invert the normal; the (signed) distance from bondee # to the plane indicates if it is on the same side # (positive == same side) d = pl.distance(bondee) if d < 0.0: normal = tinyarray.negative(normal) new_bonded.append(bondee + normal * bond_len) return new_bonded
def layout(self, params, circuit_segments): p = params from chimerax.geometry import translation, rotation, Place, vector_rotation, norm from math import pi, cos, sin # Compute helix axis direction. # The helix rotation axis is does not make a 90 degree angle with # the P-P basepair line. It makes an angle of approximately 110 degrees. pa = p.pair_tilt * pi / 180 from numpy import array axis = array((cos(pa), sin(pa), 0)) # Compute screw motion that advances basepair along helix. # Initial basepair P-P is spans (0,0,0) to (pair_width,0,0). rtf = rotation(axis, p.stem_twist, center=(0.5 * p.pair_width, 0, -p.pair_off_axis)) ttf = translation(p.pair_spacing * axis) stf = ttf * rtf # Specify initial orientations of basepair nucleotides # so P-atoms are on helix and paired P atom is in the # oriented xy plane. p0, p1 = (0, 0, 0), (p.pair_width, 0, 0) tf1 = self.stem_residue_orientation(p0, stf * p0, p1) p2 = stf.inverse() * p1 tf2 = self.stem_residue_orientation(p1, p2, p0) # Keep track of basepair P-P orientation used to # orient the loop at the end of the stem. ctf = Place() # Layout nucleotides for both strands of double helix. coords = {} for i in range(self.length): coords[self.base5p + i] = tf1 coords[self.base3p - i] = tf2 tf1 = stf * tf1 tf2 = stf * tf2 ctf = stf * ctf # The next segment after the stem should put its first # P-atom at a position on the helix so that the last # nucleotide of the stem has the right orientation to # make a base pair. To achieve this the initial basepair # P-P does not lie on the x-axis. Instead we tilt the # entire helix so the paired P is advanced up the helix # by one position. width = norm(p2) ttf = vector_rotation(p2, p1) for b, tf in coords.items(): coords[b] = ttf * coords[b] # Added circuit at end of stem. ccoords = self.circuit.layout(p, gap=width) # Position the end circuit using the P-P orientation at # the end of the stem and taking account of the tilt of # the entire helix. ctf = ttf * ctf * ttf.inverse() for b, tf in ccoords.items(): coords[b] = ctf * tf # Use segment width for the tilted helix. seg = [Segment(coords, width, 0)] return seg
def tile(session, models=None, columns=None, spacing_factor=1.3, view_all=True): """Tile models onto a square(ish) grid.""" # Keep only models that we think might work # Surfaces are excluded on the assumption that they will move # with their associated models from chimerax.atomic import Structure from chimerax.map.volume import Volume tilable_classes = [Structure, Volume] def tilable(m): for klass in tilable_classes: if isinstance(m, klass): return True return False if models is None: models = [m for m in session.models.list() if tilable(m)] models = [m for m in models if tilable(m) and m.bounds() is not None] if len(models) == 0: from chimerax.core.errors import UserError raise UserError("No models found for tiling.") # Size each tile to the maximum size (+buffer) among all models spacing = max([m.bounds().radius() for m in models]) * spacing_factor # First model is the anchor anchor = models[0].bounds().center() anchor_spec = '#' + models[0].id_string from .view import UndoView undo = UndoView("tile", session, models) with session.undo.block(): # Simultaneously ove model toward anchor in scene coordinate system # and toward final target position in screen coordinate system import math, numpy from chimerax.geometry import norm if columns is None: columns = int(round(math.sqrt(len(models)))) commands = [] for i, m in enumerate(models[1:], start=1): center = m.bounds().center() # First move in scene coordinates to superimpose model onto anchor view_delta = anchor - center view_dist = norm(view_delta) if view_dist > 0: commands.append("move %.2f,%.2f,%.2f %.2f coord %s models %s" % (view_delta[0], view_delta[1], view_delta[2], view_dist, anchor_spec, m.atomspec)) # Then move in screen coordinates to final grid position row, col = divmod(i, columns) screen_delta = numpy.array([col * spacing, -row * spacing, 0], dtype=numpy.float32) screen_dist = norm(screen_delta) if screen_dist > 0: commands.append("move %.2f,%.2f,%.2f %.2f models %s" % (screen_delta[0], screen_delta[1], screen_delta[2], screen_dist, m.atomspec)) # Make everything visible on screen if view_all: commands.append("view") from chimerax.core.commands import run run(session, "; ".join(commands), log=False) undo.finish(session, models) session.undo.register(undo) session.logger.info("%d model%s tiled" % (len(models), "s" if len(models) != 1 else ""))