コード例 #1
0
    def horseshoe_segment(self, side_length, semicircle_length, spacing,
                          start_index):
        '''
        Make a horseshoe segment with two sides each with side_length nucleotides
        parallel the y axis, curved top has semicircle_length nucleotides, spacing of
        nucleotides is spacing.  Starting nucleotide index is start_index.
        '''
        from chimerax.geometry import translation, rotation
        c = {}
        # Horseshoe side going up in y.
        ns = side_length
        b = start_index
        ls = spacing
        for i in range(ns):
            c[b + i] = translation((0, i * ls, 0)) * rotation((0, 0, 1), 90)
        from math import pi, cos, sin
        nc = semicircle_length
        r = 0.5 * ls / sin(0.5 * pi / (nc - 1))
        for i in range(nc):
            a = i * pi / (nc - 1)
            a_deg = a * 180 / pi
            ca, sa = cos(a), sin(a)
            # Horeshoe curve
            c[b + ns + i] = translation(
                (r * (1 - ca), r * sa + ns * ls, 0)) * rotation(
                    (0, 0, 1), 90 - a_deg)
        # Horseshoe side going down in y.
        for i in range(ns):
            c[b + ns + nc + i] = translation(
                (2 * r, (ns - 1 - i) * ls, 0)) * rotation((0, 0, 1), -90)

        return Segment(c, 2 * r, spacing)
コード例 #2
0
    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
コード例 #3
0
 def _exclamation_mark(self):
     v, n, t = exclamation_mark(radius=0.1, height=0.5, nc = 8)
     flip = rotation((1,0,0), 180)
     flip.transform_points(v, in_place=True)
     flip.transform_vectors(n, in_place=True)
     translation((0,1,0)).transform_points(v, in_place=True)
     d = Drawing('rotamer cb indicator')
     d.set_geometry(v, n, t)
     return d
コード例 #4
0
def pin_geometry(handle_radius, pin_radius, total_height):
    '''
    Simple 3D representation of a drawing pin.
    Args:
        handle_radius:
            The radius of the "handle" in Angstroms
        pin_radius:
            The radius of the "pin" in Angstroms
        height:
            The overall height of the drawing
    '''
    import numpy
    from chimerax.surface.shapes import cylinder_geometry, cone_geometry
    from chimerax.geometry import translation
    pin_height = total_height * 5 / 12
    handle_height = total_height * 7 / 12
    tb_height = total_height / 4

    pin = list(
        cone_geometry(radius=pin_radius, height=pin_height, points_up=False))
    handle_bottom = list(
        cone_geometry(radius=handle_radius, height=tb_height, nc=8))
    handle_middle = list(
        cylinder_geometry(radius=handle_radius / 2,
                          height=handle_height,
                          nc=8,
                          caps=False))
    handle_top = list(
        cone_geometry(radius=handle_radius,
                      height=tb_height,
                      nc=8,
                      points_up=False))

    pint = translation((0, 0, pin_height / 2))
    pin[0] = pint.transform_points(pin[0])
    hbt = translation((0, 0, pin_height + tb_height / 2.05))
    handle_bottom[0] = hbt.transform_points(handle_bottom[0])
    hmt = translation((0, 0, handle_height / 2 + pin_height))
    handle_middle[0] = hmt.transform_points(handle_middle[0])
    htt = translation((0, 0, total_height - tb_height / 2.05))
    handle_top[0] = htt.transform_points(handle_top[0])

    vertices = numpy.concatenate(
        (pin[0], handle_bottom[0], handle_middle[0], handle_top[0]))
    normals = numpy.concatenate(
        (pin[1], handle_bottom[1], handle_middle[1], handle_top[1]))

    ntri = len(pin[0])
    triangles = pin[2]
    for d in (handle_bottom, handle_middle, handle_top):
        triangles = numpy.concatenate((triangles, d[2] + ntri))
        ntri += len(d[0])

    return vertices, normals, triangles
コード例 #5
0
    def helix_segment(self, base_index, count, spacing, rise):
        '''
        Lay out a single turn helix on x axis.
        Radius is calculated to fit the specified number of nucleotides.
        '''
        # Choose radius so one helix turn has desired length.
        # Helix arc length = s, radius = r, rise = h: s**2 = r**2 + (h/(2*pi))**2
        length = spacing * count
        from math import sqrt, pi
        radius = sqrt((length / (2 * pi))**2 - (rise / (2 * pi))**2)

        # Compute screw motion to advance along helix
        angle = 2 * pi / count
        angle_deg = angle * 180 / pi
        from chimerax.geometry import translation, rotation, vector_rotation, Place
        step = translation((rise / count, 0, 0)) * rotation(
            (1, 0, 0), angle_deg, center=(0, radius, 0))

        # Compute first nucleotide so P-P lies on helix.
        orient = vector_rotation((1, 0, 0), step * (0, 0, 0))

        # Place nucleotides on helix.
        place = {}
        p = Place()
        for i in range(count):
            place[base_index + i] = p * orient
            p = step * p

        return Segment(place, rise, pad=0)
コード例 #6
0
def random_translation(bounds):

    from random import random
    shift = [x0+random()*(x1-x0) for x0,x1 in zip(bounds.xyz_min, bounds.xyz_max)]
    from chimerax.geometry import translation
    tf = translation(shift)
    return tf
コード例 #7
0
def _interpolate_camera(v1, v2, f, camera):
    c1, c2 = v1.camera, v2.camera

    # Interpolate camera position
    from chimerax.geometry import interpolate_rotation, interpolate_points
    p1, p2 = c1['position'], c2['position']
    r = interpolate_rotation(p1, p2, f)
    la = interpolate_points(v1.look_at, v2.look_at, f)
    # Look-at points in camera coordinates
    cl1 = p1.inverse() * v1.look_at
    cl2 = p2.inverse() * v2.look_at
    cla = interpolate_points(cl1, cl2, f)
    # Make camera translation so that camera coordinate look-at point
    # maps to scene coordinate look-at point r*cla + t = la.
    from chimerax.geometry import translation
    t = translation(la - r * cla)
    camera.position = t * r

    # Interpolate field of view
    if 'field_of_view' in c1 and 'field_of_view' in c2:
        camera.field_of_view = (1 - f) * c1['field_of_view'] + f * c2['field_of_view']
    elif 'field_width' in c1 and 'field_width' in c2:
        camera.field_width = (1 - f) * c1['field_width'] + f * c2['field_width']

    camera.redraw_needed = True
コード例 #8
0
    def scene_position_2d(self, hand_pose, view):
        # Adjust origin
        cpos = hand_pose.origin() - self._center

        # Scale millimeters to scene units
        cpos /= self._width  # millimeters to unit width

        # Rotate to screen orientation
        from chimerax.geometry import cross_product, orthonormal_frame
        yaxis = self._facing
        zaxis = cross_product(yaxis, self._cord)
        cpos = orthonormal_frame(zaxis, ydir=yaxis) * cpos

        # Convert to window pixels
        w, h = view.window_size
        win_xy = (w / 2 + w * cpos[0], h / 2 - h * cpos[1])

        # Map to scene near front clip plane.
        xyz_min, xyz_max = view.clip_plane_points(win_xy[0], win_xy[1])
        scene_point = (0, 0,
                       0) if xyz_min is None else (.9 * xyz_min + .1 * xyz_max)

        # Convert camera coordinates to scene coordinates
        rot = view.camera.position.zero_translation()
        from chimerax.geometry import translation
        scene_pos = translation(scene_point) * rot

        return scene_pos, win_xy
コード例 #9
0
ファイル: tube.py プロジェクト: Yongcheng123/ChimeraX
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
コード例 #10
0
ファイル: bild.py プロジェクト: Yongcheng123/ChimeraX
 def translate_command(self, tokens):
     if len(tokens) != 4:
         raise ValueError("Expected 'x y z' after %s" % tokens[0])
     data = [self.parse_float(x) for x in tokens[1:4]]
     xform = translation(data)
     self.transforms.append(self.transforms[-1] * xform)
     self.pure.append(self.pure[-1])
コード例 #11
0
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
コード例 #12
0
def random_translation_step(center, radius):

    v = random_direction()
    from random import random
    r = radius * random()
    from chimerax.geometry import translation
    tf = translation(center + r*v)
    return tf
コード例 #13
0
 def _rota_indicator(self):
     v1, n1, t1 = exclamation_mark(radius=0.1, height=0.5, nc = 8)
     v2, n2, t2 = spiral(major_radius=0.3, minor_radius = 0.05, height=0.4,
                         turn_segments=6, circle_segments=3)
     translation((0,0,-0.15)).transform_points(v2, in_place=True)
     v = numpy.concatenate((v1, v2))
     n = numpy.concatenate((n1, n2))
     t = numpy.concatenate((t1, t2+len(v1)))
     r = rotation((1,0,0),180)
     r.transform_points(v, in_place=True)
     r.transform_vectors(n, in_place=True)
     translation((0,0.5,0.25)).transform_points(v, in_place=True)
     d = Drawing('rotamer indicator')
     d.skip_bounds = True
     d.pickable = False
     d.set_geometry(v, n, t)
     return d
コード例 #14
0
 def _translate(self, shift):
     psize = self.pixel_size()
     s = tuple(dx * psize * self.speed for dx in shift)  # Scene units
     step = self.camera_position.transform_vector(s)  # Scene coord system
     if self._moving_atoms:
         from chimerax.geometry import translation
         self._move_atoms(translation(step))
     else:
         self.view.translate(step, self.models())
コード例 #15
0
ファイル: snav.py プロジェクト: Yongcheng123/ChimeraX
    def apply_transform(self, tf):

        v = self.view
        cam = v.camera
        cp = cam.position
        cpinv = cp.inverse()
        if self.fly_mode:
            cr = cpinv * cam.position.origin()
            tf = tf.inverse()
        else:
            if tf.rotation_angle() <= 1e-5:
                v._update_center_of_rotation = True  # Translation
            cr = cpinv * v.center_of_rotation
        from chimerax.geometry import translation
        stf = cp * translation(cr) * tf * translation(-cr) * cpinv
        if self.collision(stf.inverse() * cam.position.origin()):
            return
        v.move(stf)
コード例 #16
0
 def _orient(self):
     gc = []
     la = []
     for g, (x, y) in zip(self.groups, self._layout_positions):
         if g.shown():
             gc.append(g.centroid())
             la.append((x, y, 0))
     if len(gc) < 2:
         return
     from chimerax.geometry import align_points, translation
     from numpy import array, mean
     p, rms = align_points(array(gc), array(la))
     ra = p.zero_translation()
     center = mean(gc, axis=0)
     v = self._session().main_view
     rc = v.camera.position.zero_translation()
     rot = translation(center) * rc * ra * translation(-center)
     v.move(rot)
コード例 #17
0
 def _update_geometry(self):
     from chimerax.shape.shape import cylinder_geometry
     varray, tarray = cylinder_geometry(
         self.radius, self.thickness, max(2, int(self.thickness / 3 + 0.5)),
         max(40, int(20.0 * self.radius + 0.5)), True)
     from chimerax.geometry import translation, vector_rotation
     varray = (translation(self.plane.origin) * vector_rotation(
         (0, 0, 1), self.plane.normal)).transform_points(varray)
     from chimerax.surface import calculate_vertex_normals
     narray = calculate_vertex_normals(varray, tarray)
     self.set_geometry(varray, narray, tarray)
コード例 #18
0
ファイル: meeting.py プロジェクト: Yongcheng123/ChimeraX
 def update_pointer(self, msg):
     if 'name' in msg:
         if 'id' in msg:  # If id not in msg leave name as "my pointer".
             self.name = '%s pointer' % msg['name']
     if 'color' in msg:
         self.color = msg['color']
     if 'mouse' in msg:
         xyz, axis = msg['mouse']
         from chimerax.geometry import vector_rotation, translation
         p = translation(xyz) * vector_rotation((0, 0, 1), axis)
         self.position = p
コード例 #19
0
def correlation(v, xyz, w, axis, angle, center, rise=None):

    from chimerax.geometry import rotation, translation
    tf = rotation(axis, angle, center)
    if not rise is None:
        shift = translation([x * rise for x in axis])
        tf = shift * tf
    xf = v.scene_position * tf
    wtf = v.interpolated_values(xyz, xf)
    from chimerax.map_fit.fitmap import overlap_and_correlation
    olap, cor, corm = overlap_and_correlation(w, wtf)
    return cor, corm
コード例 #20
0
 def __init__(self, name, session, color, center, radius):
     self._num_triangles = 1000
     Model.__init__(self, name, session)
     from chimerax.surface import sphere_geometry2
     va, na, ta = sphere_geometry2(self._num_triangles)
     self._unit_vertices = va
     self.set_geometry(radius * va, na, ta)
     self.color = color
     from chimerax.geometry import translation
     self.position = translation(center)
     self._radius = radius
     session.models.add([self])
コード例 #21
0
 def translate(self, shift, drawings=None):
     '''Move camera to simulate a translation of drawings.  Translation
     is in scene coordinates.'''
     if shift[0] == 0 and shift[1] == 0 and shift[2] == 0:
         return
     if self._center_of_rotation_method in ('front center',
                                            'center of view'):
         self._update_center_of_rotation = True
     self._shift_near_far_clip_planes(shift)
     from chimerax.geometry import translation
     t = translation(shift)
     self.move(t, drawings)
コード例 #22
0
ファイル: depthmask.py プロジェクト: Yongcheng123/ChimeraX
def surface_projection_coordinates(surfaces, projection_axis, volume):

    g = volume.data

    # Scale rotated surface coordinates to grid index units.
    axis_aligned = (tuple(projection_axis) in ((1, 0, 0), (0, 1, 0), (0, 0, 1))
                    and tuple(g.cell_angles) == (90, 90, 90)
                    and g.rotation == ((1, 0, 0), (0, 1, 0), (0, 0, 1)))
    if axis_aligned:
        grid_spacing = g.step
    else:
        s = min(g.plane_spacings())
        grid_spacing = (s, s, s)

    # Determine transform from vertex coordinates to depth array indices
    # Rotate projection axis to z.
    from chimerax.geometry import orthonormal_frame, scale, translation
    tfrs = orthonormal_frame(projection_axis).inverse() * scale(
        [1 / s for s in grid_spacing])

    # Transform vertices to depth array coordinates.
    zsurf = []
    tcount = 0
    for vertices, triangles in surfaces:
        varray = tfrs.transform_points(vertices)
        zsurf.append((varray, triangles))
        tcount += len(triangles)
    if tcount == 0:
        return None

    # Compute origin for depth grid
    vmin, vmax = bounding_box(zsurf)
    if axis_aligned:
        o = tfrs * g.origin
        offset = [(vmin[a] - o[a]) for a in (0, 1, 2)]
        from math import floor
        align_frac = [offset[a] - floor(offset[a]) for a in (0, 1, 2)]
        vmin -= align_frac
    else:
        vmin -= 0.5

    tf = translation(-vmin) * tfrs

    # Shift surface vertices by depth grid origin
    for varray, triangles in zsurf:
        varray -= vmin

    # Compute size of depth grid
    from math import ceil
    size = tuple(int(ceil(vmax[a] - vmin[a] + 1)) for a in (0, 1))

    return zsurf, size, tf
コード例 #23
0
def unbend_volume(volume,
                  path,
                  yaxis,
                  xsize,
                  ysize,
                  grid_spacing,
                  subregion='all',
                  step=1,
                  model_id=None):

    # Compute correctly spaced cubic splined path points.
    points = spline_path(path, grid_spacing)
    axes = path_point_axes(points, yaxis)
    nx = int(xsize / grid_spacing) + 1
    ny = int(ysize / grid_spacing) + 1
    nz = len(points)

    # Create a rectangle of point positions to interpolate at.
    from numpy import empty, float32, arange
    section = empty((ny, nx, 3), float32)
    x = arange(nx, dtype=float32) * grid_spacing - 0.5 * (xsize - 1.0)
    y = arange(ny, dtype=float32) * grid_spacing - 0.5 * (ysize - 1.0)
    for j in range(ny):
        section[j, :, 0] = x
    for i in range(nx):
        section[:, i, 1] = y
    section[:, :, 2] = 0
    s = section.reshape((ny * nx, 3))

    # Interpolate planes to fill straightened array.
    from chimerax.geometry import translation
    m = empty((nz, ny, nx), float32)
    for k in range(nz):
        tf = translation(points[k]) * axes[k]
        m[k, :, :] = volume.interpolated_values(s,
                                                tf,
                                                subregion=subregion,
                                                step=step).reshape((ny, nx))

    # Create volume.
    from chimerax.map_data import ArrayGridData
    step = [grid_spacing] * 3
    origin = [0, 0, 0]
    g = ArrayGridData(m, origin, step, name='unbend')
    from chimerax.map import volume_from_grid_data
    v = volume_from_grid_data(g, volume.session, model_id=model_id)
    v.copy_settings_from(volume,
                         copy_region=False,
                         copy_active=False,
                         copy_xform=open)

    return v
コード例 #24
0
ファイル: zoom.py プロジェクト: Yongcheng123/ChimeraX
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
コード例 #25
0
 def update_position(self, *_):
     m = self.model
     fr = self.frame
     if fr >= self.frames:
         m.position = self.pos1
         from chimerax.core.triggerset import DEREGISTER
         return DEREGISTER
     else:
         f = fr / self.frames
         from chimerax.geometry import translation, rotation
         m.position = translation(f * (self.c1 - self.c0)) * rotation(
             self.axis, f * self.angle, self.c0) * self.pos0
         self.frame += 1
コード例 #26
0
def post_geometry(radius, height, caps=False):
    '''
    Returns a simple cylinder, rotated and translated so its base is on
    the origin and it points along (1,0,0)
    '''
    from chimerax.surface.shapes import cylinder_geometry
    from chimerax.geometry import rotation, translation
    v, n, t = cylinder_geometry(radius=radius, height=height, caps=caps, nc=6)
    tr = translation([0, 0, height / 2])
    tr.transform_points(v, in_place=True)
    r = rotation([0, 1, 0], 90)
    r.transform_points(v, in_place=True)
    r.transform_vectors(n, in_place=True)
    return v, n, t
コード例 #27
0
 def move_camera_and_cofr(self, dz):
     cofr = self.view.center_of_rotation
     cofr_method = self.view.center_of_rotation_method
     camera = self.view.camera
     cpos = self.camera_position.origin()
     vd = camera.view_direction()
     import numpy
     cc = cofr-cpos
     shift_vec = numpy.dot(cc/numpy.linalg.norm(cc), vd) *vd * dz
     from chimerax.geometry import translation
     t = translation(shift_vec)
     self.view.center_of_rotation += shift_vec
     self.view.center_of_rotation_method = cofr_method
     camera.set_position(t*camera.position)
コード例 #28
0
    def straight_layout(self, segs, params):
        '''Layout on x-axis starting at the origin, proceeding in positive x direction.'''

        coords = {}
        s = 0
        from chimerax.geometry import translation, rotation
        for i, seg in enumerate(segs):
            rtf = rotation((1, 0, 0), params.branch_twist * i)
            stf = translation((s, 0, 0))
            ptf = stf * rtf
            for b, tf in seg.placements.items():
                coords[b] = ptf * tf
            # Next position along line
            s += seg.width + seg.pad

        return coords
コード例 #29
0
ファイル: cover.py プロジェクト: Yongcheng123/ChimeraX
def map_covering_box(v, ijk_min, ijk_max, ijk_cell_size, symmetries, step):

    d = v.data
    if ijk_cell_size == d.size and (symmetries is None
                                    or len(symmetries) == 0):
        # Full unit cell and no symmetries to average.
        g = v.grid_data(subregion='all', step=step, mask_zone=False)
        from chimerax.map import volume
        cg = volume.map_from_periodic_map(g, ijk_min, ijk_max)
        return cg

    out_ijk_size = tuple(a - b + 1 for a, b in zip(ijk_max, ijk_min))
    from chimerax.geometry import translation
    out_ijk_to_vijk_transform = translation(ijk_min)

    ijk_symmetries = ijk_symmetry_matrices(d, symmetries)

    from numpy import empty, float32
    shape = list(reversed(out_ijk_size))
    m = empty(shape, float32)

    from chimerax.map import extend_crystal_map
    nnc, dmax = extend_crystal_map(v.full_matrix(), ijk_cell_size,
                                   ijk_symmetries.array(), m,
                                   out_ijk_to_vijk_transform.matrix)

    log = v.session.logger
    log.info(
        'Extended map %s to box of size (%d, %d, %d),\n' %
        ((v.name, ) + out_ijk_size) +
        '  cell size (%d, %d, %d) grid points, %d symmetry operations,\n' %
        (tuple(ijk_cell_size) + (len(ijk_symmetries), )) +
        '  %d points not covered by any symmetry,\n' % nnc +
        '  maximum value difference where symmetric map copies overlap = %.5g\n'
        % dmax)
    if nnc > 0:
        log.status('%d grid points not covered' % nnc)

    origin = d.ijk_to_xyz(ijk_min)
    from chimerax.map_data import ArrayGridData
    g = ArrayGridData(m,
                      origin,
                      d.step,
                      d.cell_angles,
                      name=v.name + ' extended')

    return g
コード例 #30
0
def arrow_along_force_vector(arrow, xyz0, force, scale=1.0):
    '''
    Takes an arrow drawn by simple_arrow and rotates, translates and scales
    it to point from xyz0 along a force vector, with length scaled according
    to the magnitude of the force.
    '''
    from chimerax.geometry.place import translation, vector_rotation, scale, Place, product
    import numpy

    # Original arrow points along the z axis
    u = numpy.array((0, 0, 1), dtype='float32')
    # Get vector length
    l = numpy.linalg.norm(force)
    rot = vector_rotation(u, force / l)
    trans = translation(xyz0)
    sc = scale(l)
    arrow.position = product((trans, rot, sc))