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 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 z_plane_spacing(self): dz = self._z_spacing if dz is None: files = self._file_info if self.multiframe: f = files[0] fpos = f._frame_positions if fpos is None: gfov = f._grid_frame_offset_vector if gfov is None: dz = None else: dz = self._spacing(gfov) else: # TODO: Need to reverse order if z decrease as frame number increases z_axis = self.plane_normal() from chimerax.geometry import inner_product z = [inner_product(fp, z_axis) for fp in fpos] dz = self._spacing(z) if dz is not None and dz < 0: self._reverse_frames = True dz = abs(dz) elif len(files) < 2: dz = None else: nz = self.grid_size()[2] # For time series just look at first time point. z_axis = self.plane_normal() from chimerax.geometry import inner_product z = [inner_product(f._position, z_axis) for f in files[:nz]] dz = self._spacing(z) self._z_spacing = dz return dz
def clip_segment_with_planes(xyz_1, xyz_2, planes): f1 = 0 f2 = 1 for normal, offset in planes: c1 = inner_product(normal, xyz_1) - offset c2 = inner_product(normal, xyz_2) - offset if c1 < 0 and c2 < 0: return None, None, None, None # All of segment is clipped if c1 >= 0 and c2 >= 0: continue # None of segment is clipped f = c1 / (c1 - c2) if c1 < 0: f1 = max(f1, f) else: f2 = min(f2, f) if f1 == 0 and f2 == 1: return xyz_1, xyz_2, f1, f2 if f1 > f2: return None, None, None, None i1 = [(1 - f1) * a + f1 * b for a, b in zip(xyz_1, xyz_2)] # Intercept point i2 = [(1 - f2) * a + f2 * b for a, b in zip(xyz_1, xyz_2)] # Intercept point return i1, i2, f1, f2
def _closest_point(self, event): '''Project start point to view line through mouse position.''' xyz1, xyz2 = self._view_line(event) p = self._start_point dx = xyz2 - xyz1 from chimerax.geometry import inner_product f = inner_product(p - xyz1, dx) / inner_product(dx, dx) cp = xyz1 + f * dx return cp
def pull_direction(self, atom): v = atom.structure.session.main_view x0, x1 = v.clip_plane_points(self.x, self.y) axyz = atom.scene_coord # Project atom onto view ray to get displacement. dir = x1 - x0 da = axyz - x0 from chimerax.geometry import inner_product offset = da - (inner_product(da, dir) / inner_product(dir, dir)) * dir return axyz, -offset
def _offset_vector(self, x, y, starting_coord): v = self.session.main_view x0, x1 = v.clip_plane_points(x, y) axyz = starting_coord # Project atom onto view ray to get displacement. dir = x1 - x0 da = axyz - x0 from chimerax.geometry import inner_product offset = self.structure.scene_position.inverse( is_orthonormal=True).transform_vectors( da - (inner_product(da, dir) / inner_product(dir, dir)) * dir) return -offset
def _far_clip_point(self, pos): view = self.session.main_view cam = view.camera view_num = 0 cam_pos = cam.get_position(view_num).origin() cam_dir = cam.view_direction(view_num) pick_dir = pos - cam_pos near, far = view.near_far_distances(cam, view_num) from chimerax.geometry import inner_product denom = inner_product(pick_dir, cam_dir) d = (far - inner_product(cam_pos, cam_dir)) / denom if denom != 0 else 1000 return cam_pos + d * pick_dir
def next_edge(p, direction, edges, vertices): from chimerax.geometry import cross_product, inner_product for e in edges: ev1, ev2 = vertices[e[0]] - p, vertices[e[1]] - p n = cross_product(ev1, ev2) tn = cross_product(direction, n) i1, i2 = inner_product(tn, ev1), inner_product(tn, ev2) if (i1 < 0 and i2 > 0) or (i1 > 0 and i2 < 0): # Edge is split by geodesic direction f = i1 / (i1 - i2) p2 = (1 - f) * vertices[e[0]] + f * vertices[e[1]] return e, f return None, None
def near_far_distances(self, camera, view_num, include_clipping=True): '''Near and far scene bounds as distances from camera.''' cp = camera.get_position(view_num).origin() vd = camera.view_direction(view_num) near, far = self._near_far_bounds(cp, vd) if include_clipping: p = self.clip_planes np, fp = p.find_plane('near'), p.find_plane('far') from chimerax.geometry import inner_product if np: near = max(near, inner_product(vd, (np.plane_point - cp))) if fp: far = min(far, inner_product(vd, (fp.plane_point - cp))) cnear, cfar = self._clamp_near_far(near, far) return cnear, cfar
def _transform_schematic(session, transform, center, from_rgba, to_rgba, length, width, thickness): axis, rot_center, angle_deg, shift = transform.axis_center_angle_shift() # Align rot_center at same position along axis as center. from chimerax.geometry import inner_product rot_center += inner_product(center - rot_center, axis) * axis width_axis = center - rot_center varray, narray, tarray = _axis_square(axis, rot_center, width_axis, length, width, thickness) from chimerax.core.models import Model, Surface s1 = Surface('slab 1', session) s1.set_geometry(varray, narray, tarray) s1.color = from_rgba s2 = Surface('slab 2', session) from chimerax.geometry import rotation, translation rot2 = translation(shift * axis) * rotation( axis, angle_deg, center=rot_center) varray2 = rot2 * varray narray2 = rot2.transform_vectors(narray) s2.set_geometry(varray2, narray2, tarray) s2.color = to_rgba m = Model('transform schematic', session) m.add([s1, s2]) return m
def optimize_helix_paramters(v, xyz, w, axis, rise, angle, center): m = v.full_matrix() xyz_to_ijk_transform = v.data.xyz_to_ijk_transform max_steps = 2000 ijk_step_size_min, ijk_step_size_max = 0.01, 0.5 optimize_translation = optimize_rotation = True metric = 'correlation' from chimerax.geometry import helical_symmetry_matrix htf = helical_symmetry_matrix(rise, angle, axis, center) tf = xyz_to_ijk_transform * htf from chimerax.map_fit.fitmap import locate_maximum move_tf, stats = locate_maximum(xyz, w, m, tf, max_steps, ijk_step_size_min, ijk_step_size_max, optimize_translation, optimize_rotation, metric) ohtf = htf * move_tf oaxis, ocenter, oangle, orise = ohtf.axis_center_angle_shift() from chimerax.geometry import inner_product if inner_product(axis, oaxis) < 0: orise = -orise oangle = -oangle corr = stats['correlation'] return orise, oangle, corr
def scene_position_6d(self, hand_pose, view): from chimerax.geometry import translation, cross_product from chimerax.geometry import orthonormal_frame, inner_product # Adjust origin cpos = translation(-self._center) * hand_pose # Scale millimeters to scene units scene_center = view.center_of_rotation cam = view.camera factor = cam.view_width(scene_center) / self._width cpos = cpos.scale_translation(factor) # millimeters to scene units # Rotate to screen orientation yaxis = self._facing zaxis = cross_product(yaxis, self._cord) cpos = orthonormal_frame(zaxis, ydir=yaxis) * cpos # Adjust depth origin to center of rotation. cam_pos = cam.position depth = inner_product(scene_center - cam_pos.origin(), -cam_pos.z_axis()) cpos = translation( (0, 0, -depth)) * cpos # Center in front of camera (-z axis). # Convert camera coordinates to scene coordinates scene_pos = cam_pos * cpos # Map from camera to scene coordinates return scene_pos
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 compute_instances_cap(drawing, triangles, plane, offset): d = drawing doffset = offset + getattr(d, 'clip_offset', 0) point = plane.plane_point normal = plane.normal b = d.geometry_bounds() if b is None: return None, None, None dpos = d.get_scene_positions(displayed_only=True) ipos = box_positions_intersecting_plane(dpos, b, point, normal) if len(ipos) == 0: return None, None, None geom = [] for pos in ipos: pinv = pos.inverse() pnormal = pinv.transform_vector(normal) from chimerax.geometry import inner_product poffset = inner_product(pnormal, pinv * point) + doffset from . import compute_cap ivarray, itarray = compute_cap(-pnormal, -poffset, d.vertices, triangles) pos.transform_points(ivarray, in_place=True) geom.append((ivarray, itarray)) varray, tarray = concatenate_geometry(geom) return varray, tarray, normal
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 perspective_view_all(bounds, position, field_of_view, window_size=None, pad=0): ''' Return the camera position that shows the specified bounds. Camera has perspective projection. ''' from math import radians, sin, cos fov2 = radians(field_of_view) / 2 s, c = sin(fov2), cos(fov2) face_normals = [ position.transform_vector(v) for v in ((c / s, 0, 1), (-c / s, 0, 1)) ] # frustum side normals if window_size is not None: aspect = window_size[1] / window_size[0] from math import tan, atan fov2y = atan(aspect * tan(fov2)) sy, cy = sin(fov2y), cos(fov2y) face_normals.extend([ position.transform_vector(v) for v in ((0, cy / sy, 1), (0, -cy / sy, 1)) ]) # frustum top/bottom normals center = bounds.center() bc = bounds.box_corners() - center from chimerax.geometry import inner_product, Place d = max(inner_product(n, c) for c in bc for n in face_normals) d *= 1 / max(0.01, 1 - pad) view_direction = -position.z_axis() camera_center = center - d * view_direction va_position = Place(axes=position.axes(), origin=camera_center) return va_position
def clip_move(self, delta_xy, front_shift, back_shift, delta=None): pf, pb = self._planes(front_shift, back_shift) if pf is None and pb is None: return from chimerax.graphics import SceneClipPlane, CameraClipPlane p = pf or pb if delta is not None: d = delta elif isinstance(p, SceneClipPlane): # Move scene clip plane d = self._tilt_shift(delta_xy, self.view.camera, p.normal) elif isinstance(p, CameraClipPlane): # near/far clip d = delta_xy[1] * self.pixel_size() # Check if slab thickness becomes less than zero. dt = -d * (front_shift + back_shift) if pf and pb and dt < 0: from chimerax.geometry import inner_product sep = inner_product(pb.plane_point - pf.plane_point, pf.normal) if sep + dt <= 0: # Would make slab thickness less than zero. return if pf: pf.plane_point = pf.plane_point + front_shift * d * pf.normal if pb: pb.plane_point = pb.plane_point + back_shift * d * pb.normal
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 point_in_view(point, view): w, h = view.window_size cam = view.camera # Compute planes bounding view planes = cam.rectangle_bounding_planes((0, 0), (w, h), (w, h)) from chimerax.geometry import inner_product for p in planes: if inner_product(point, p[:3]) + p[3] < 0: return False # Check near/far planes near, far = view.near_far_distances(cam, view_num=0) dist = inner_product(point - cam.position.origin(), cam.view_direction()) if dist < near or dist > far: return False return True
def circle_in_circles(i, circles): from chimerax.geometry import inner_product p, a = circles[i].center, circles[i].angle for j, c in enumerate(circles): if c.angle >= a and inner_product( p, c.center) >= cos(c.angle - a) and j != i: return True return False
def _near_far_bounds(self, camera_pos, view_dir): b = self.drawing_bounds(allow_drawing_changes=False) if b is None: return self._min_near_fraction, 1 # Nothing shown from chimerax.geometry import inner_product d = inner_product(b.center() - camera_pos, view_dir) # camera to center of drawings r = (1 + self._near_far_pad) * b.radius() return (d - r, d + r)
def _shift_near_far_clip_planes(self, shift): p = self.clip_planes np, fp = p.find_plane('near'), p.find_plane('far') if np or fp: vd = self.camera.view_direction() from chimerax.geometry import inner_product plane_shift = inner_product(shift, vd) * vd if np: np.plane_point += plane_shift if fp: fp.plane_point += plane_shift
def circle_intercepts(c0, c1): from chimerax.geometry import inner_product, cross_product ca01 = inner_product(c0.center, c1.center) x01 = cross_product(c0.center, c1.center) s2 = inner_product(x01, x01) if s2 == 0: return None, None ca0 = c0.cos_angle ca1 = c1.cos_angle a = (ca0 - ca01 * ca1) / s2 b = (ca1 - ca01 * ca0) / s2 d2 = (s2 - ca0 * ca0 - ca1 * ca1 + 2 * ca01 * ca0 * ca1) if d2 < 0: return None, None d = sqrt(d2) / s2 return (a * c0.center + b * c1.center - d * x01, a * c0.center + b * c1.center + d * x01)
def set_focus_depth(self, point_on_screen, window_width): from chimerax.geometry import inner_product z = inner_product(self.view_direction(), point_on_screen - self.position.origin()) if z <= 0: return from math import tan, radians screen_width = 2 * z * tan(0.5 * radians(self.field_of_view)) es = screen_width * self.eye_separation_pixels / window_width self.eye_separation_scene = es self.redraw_needed = True
def vr_motion(self, event): # Virtual reality hand controller motion. br = self._bond_rot if br: axis, angle = event.motion.rotation_axis_and_angle() from chimerax.geometry import inner_product if inner_product(axis, br.axis) < 0: angle = -angle angle_change = self._speed_factor * angle if abs(angle_change) < self._minimum_angle_step: return "accumulate drag" br.angle += angle_change
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 _update_focus_depth(self): v = self._session.main_view b = v.drawing_bounds() if b is None: return view_dir = -self.position.z_axis() delta = b.center() - self.position.origin() from chimerax.geometry import inner_product d = inner_product(delta, view_dir) d -= self._depth_offset if d != self._focus_depth: self._focus_depth = d self._redraw_needed()
def zoom_camera(c, point, factor): if hasattr(c, 'field_width'): # Orthographic camera c.field_width /= factor else: # Perspective camera v = c.view_direction() p = c.position from chimerax.geometry import inner_product, translation delta_z = inner_product(p.origin() - point, v) zmove = (delta_z * (1 - factor) / factor) * v c.position = translation(zmove) * p c.redraw_needed = True
def vr_motion(self, event): v = self._map if v is None: return trans = event.tip_motion dxyz = v.scene_position.inverse().transform_vector(trans) ro = v.rendering_options from chimerax.geometry import inner_product dist = inner_product(ro.tilted_slab_axis, dxyz) self._move_slab(dist) center = event.tip_position axis, angle = event.motion.rotation_axis_and_angle() self._rotate_slab(axis, angle, center)