Ejemplo n.º 1
0
def get_grasps(world, name, grasp_types=GRASP_TYPES, pre_distance=APPROACH_DISTANCE, **kwargs):
    use_width = world.robot_name == FRANKA_CARTER
    body = world.get_body(name)
    #fraction = 0.25
    obj_type = type_from_name(name)
    body_pose = REFERENCE_POSE.get(obj_type, unit_pose())
    center, extent = approximate_as_prism(body, body_pose)

    for grasp_type in grasp_types:
        if not implies(world.is_real(), is_valid_grasp_type(name, grasp_type)):
            continue
        #assert is_valid_grasp_type(name, grasp_type)
        if grasp_type == TOP_GRASP:
            grasp_length = 1.5 * FINGER_EXTENT[2]  # fraction = 0.5
            pre_direction = pre_distance * get_unit_vector([0, 0, 1])
            post_direction = unit_point()
            generator = get_top_grasps(body, under=True, tool_pose=TOOL_POSE, body_pose=body_pose,
                                       grasp_length=grasp_length, max_width=np.inf, **kwargs)
        elif grasp_type == SIDE_GRASP:
            # Take max of height and something
            grasp_length = 1.75 * FINGER_EXTENT[2]  # No problem if pushing a little
            x, z = pre_distance * get_unit_vector([3, -1])
            pre_direction = [0, 0, x]
            post_direction = [0, 0, z]
            top_offset = extent[2] / 2 if obj_type in MID_SIDE_GRASPS else 1.0*FINGER_EXTENT[0]
            # Under grasps are actually easier for this robot
            # TODO: bug in under in that it grasps at the bottom
            generator = get_side_grasps(body, under=False, tool_pose=TOOL_POSE, body_pose=body_pose,
                                        grasp_length=grasp_length, top_offset=top_offset, max_width=np.inf, **kwargs)
            # if world.robot_name == FRANKA_CARTER else unit_pose()
            generator = (multiply(Pose(euler=Euler(yaw=yaw)), grasp)
                         for grasp in generator for yaw in [0, np.pi])
        else:
            raise ValueError(grasp_type)
        grasp_poses = randomize(list(generator))
        if obj_type in CYLINDERS:
            # TODO: filter first
            grasp_poses = (multiply(grasp_pose, Pose(euler=Euler(
                yaw=random.uniform(-math.pi, math.pi)))) for grasp_pose in cycle(grasp_poses))
        for i, grasp_pose in enumerate(grasp_poses):
            pregrasp_pose = multiply(Pose(point=pre_direction), grasp_pose,
                                     Pose(point=post_direction))
            grasp = Grasp(world, name, grasp_type, i, grasp_pose, pregrasp_pose)
            with BodySaver(body):
                grasp.get_attachment().assign()
                with BodySaver(world.robot):
                    grasp.grasp_width = close_until_collision(
                        world.robot, world.gripper_joints, bodies=[body])
            #print(get_joint_positions(world.robot, world.arm_joints)[-1])
            #draw_pose(unit_pose(), parent=world.robot, parent_link=world.tool_link)
            #grasp.get_attachment().assign()
            #wait_for_user()
            ##for value in get_joint_limits(world.robot, world.arm_joints[-1]):
            #for value in [-1.8973, 0, +1.8973]:
            #    set_joint_position(world.robot, world.arm_joints[-1], value)
            #    grasp.get_attachment().assign()
            #    wait_for_user()
            if use_width and (grasp.grasp_width is None):
                continue
            yield grasp
Ejemplo n.º 2
0
Archivo: push.py Proyecto: lyltc1/LTAMP
def sample_push_contact(world, feature, parameter, under=False):
    robot = world.robot
    arm = feature['arm_name']
    body = world.get_body(feature['block_name'])
    push_yaw = feature['push_yaw']

    center, (width, _, height) = approximate_as_prism(body, body_pose=Pose(euler=Euler(yaw=push_yaw)))
    max_backoff = width + 0.1 # TODO: add gripper bounding box
    tool_link = link_from_name(robot, TOOL_FRAMES[arm])
    tool_pose = get_link_pose(robot, tool_link)
    gripper_link = link_from_name(robot, GRIPPER_LINKS[arm])
    collision_links = get_link_subtree(robot, gripper_link)

    urdf_from_center = Pose(point=center)
    reverse_z = Pose(euler=Euler(pitch=math.pi))
    rotate_theta = Pose(euler=Euler(yaw=push_yaw))
    #translate_z = Pose(point=Point(z=-feature['block_height']/2. + parameter['gripper_z'])) # Relative to base
    translate_z = Pose(point=Point(z=parameter['gripper_z'])) # Relative to center
    tilt_gripper = Pose(euler=Euler(pitch=parameter['gripper_tilt']))

    grasps = []
    for i in range(1 + under):
        flip_gripper = Pose(euler=Euler(yaw=i * math.pi))
        for x in np.arange(0, max_backoff, step=0.01):
            translate_x = Pose(point=Point(x=-x))
            grasp_pose = multiply(flip_gripper, tilt_gripper, translate_x, translate_z, rotate_theta,
                                  reverse_z, invert(urdf_from_center))
            set_pose(body, multiply(tool_pose, TOOL_POSE, grasp_pose))
            if not link_pairs_collision(robot, collision_links, body, collision_buffer=0.):
                grasps.append(grasp_pose)
                break
    return grasps
Ejemplo n.º 3
0
def get_scoop_feature(world, bowl_name, spoon_name):
    bowl_body = world.get_body(bowl_name)
    _, (bowl_d, bowl_h) = approximate_as_cylinder(bowl_body)
    bowl_vertices = vertices_from_rigid(bowl_body)
    #bowl_mesh = read_obj(load_cup_bowl_obj(get_type(bowl_name))[0])
    #print(len(bowl_vertices), len(bowl_mesh.vertices))

    spoon_c, (spoon_w, spoon_l, spoon_h) = approximate_as_prism(
        world.get_body(spoon_name),
        body_pose=(unit_point(), lookup_orientation(spoon_name, STIR_QUATS)))

    # TODO: compute moments/other features from the mesh
    feature = {
        'bowl_name': bowl_name,
        'bowl_type': get_type(bowl_name),
        'bowl_diameter': bowl_d,
        'bowl_height': bowl_h,
        'bowl_base_diameter': compute_base_diameter(bowl_vertices),

        # In stirring orientation
        'spoon_name': spoon_name,
        'spoon_type': get_type(spoon_name),
        'spoon_width': spoon_w,
        'spoon_length': spoon_l,
        'spoon_height': spoon_h,
    }
    return feature
Ejemplo n.º 4
0
Archivo: pour.py Proyecto: lyltc1/LTAMP
def get_pour_feature(world, bowl_name, cup_name):
    bowl_body = world.get_body(bowl_name)
    bowl_reference = get_reference_pose(bowl_name)
    _, (bowl_d, bowl_h) = approximate_as_cylinder(bowl_body,
                                                  body_pose=bowl_reference)
    bowl_vertices = vertices_from_rigid(bowl_body)
    #bowl_mesh = read_obj(load_cup_bowl_obj(get_type(bowl_name))[0])
    #print(len(bowl_vertices), len(bowl_mesh.vertices))

    cup_body = world.get_body(cup_name)
    cup_reference = (unit_point(), get_liquid_quat(cup_name))
    _, (cup_d, _, cup_h) = approximate_as_prism(cup_body,
                                                body_pose=cup_reference)
    cup_vertices = vertices_from_rigid(cup_body)
    #cup_mesh = read_obj(load_cup_bowl_obj(get_type(cup_name))[0])

    # TODO: compute moments/other features from the mesh
    feature = {
        'bowl_name': bowl_name,
        'bowl_type': get_type(bowl_name),
        'bowl_diameter': bowl_d,
        'bowl_height': bowl_h,
        'bowl_base_diameter': compute_base_diameter(bowl_vertices),
        'cup_name': cup_name,
        'cup_type': get_type(cup_name),
        'cup_diameter': cup_d,
        'cup_height': cup_h,
        'cup_base_diameter': compute_base_diameter(cup_vertices),
    }
    return feature
Ejemplo n.º 5
0
def pour_beads(world,
               cup_name,
               beads,
               reset_contained=False,
               fix_outside=True,
               cup_thickness=0.01,
               bead_offset=0.01,
               drop_rate=0.02,
               **kwargs):
    if not beads:
        return set()
    start_time = time.time()
    # TODO: compute the radius of each bead
    bead_radius = np.average(approximate_as_prism(beads[0])) / 2.

    masses = list(map(get_mass, beads))
    savers = list(map(BodySaver, beads))
    for body in beads:
        set_mass(body, 0)

    cup = world.get_body(cup_name)
    local_center, (diameter, height) = approximate_as_cylinder(cup)
    center = get_point(cup) + local_center
    interior_radius = max(0.0, diameter / 2. - bead_radius - cup_thickness)
    # TODO: fill up to a certain threshold

    ty = get_type(cup_name)
    if ty in SPOON_DIAMETERS:
        # TODO: do this in a more principled way
        interior_radius = 0
        center[1] += (SPOON_LENGTHS[ty] - SPOON_DIAMETERS[ty]) / 2.

    # TODO: some sneak out through the bottom
    # TODO: could reduce gravity while filling
    world.controller.set_gravity()
    for i, bead in enumerate(beads):
        # TODO: halton sequence
        x, y = center[:2] + np.random.uniform(
            0, interior_radius) * unit_from_theta(
                np.random.uniform(-np.pi, np.pi))
        new_z = center[2] + height / 2. + bead_radius + bead_offset
        set_point(bead, [x, y, new_z])
        set_mass(bead, masses[i])
        world.controller.rest_for_duration(drop_rate)
    world.controller.rest_for_duration(BEADS_REST)
    print('Simulated {} beads in {:3f} seconds'.format(
        len(beads), elapsed_time(start_time)))
    contained_beads = get_contained_beads(cup, beads, **kwargs)
    #wait_for_user()

    for body in beads:
        if fix_outside and (body not in contained_beads):
            set_mass(body, 0)
    for saver in savers:
        if reset_contained or (saver.body not in contained_beads):
            saver.restore()
    #wait_for_user()
    return contained_beads
Ejemplo n.º 6
0
def pour_path_from_parameter(world, bowl_name, cup_name):
    bowl_body = world.get_body(bowl_name)
    bowl_center, (bowl_d, bowl_h) = approximate_as_cylinder(bowl_body)
    cup_body = world.get_body(cup_name)
    cup_center, (cup_d, _, cup_h) = approximate_as_prism(cup_body)

    #####

    obj_type = type_from_name(cup_name)
    if obj_type in [MUSTARD]:
        initial_pitch = final_pitch = -np.pi
        radius = 0
    else:
        initial_pitch = 0  # different if mustard
        final_pitch = -3 * np.pi / 4
        radius = bowl_d / 2

    #axis_in_cup_center_x = -0.05
    axis_in_cup_center_x = 0  # meters
    #axis_in_cup_center_z = -cup_h/2.
    axis_in_cup_center_z = 0.  # meters
    #axis_in_cup_center_z = +cup_h/2.

    # tl := top left | tr := top right
    cup_tl_in_center = np.array([-cup_d / 2, 0, cup_h / 2])
    cup_tl_in_axis = cup_tl_in_center - Point(z=axis_in_cup_center_z)
    cup_tl_angle = np.math.atan2(cup_tl_in_axis[2], cup_tl_in_axis[0])
    cup_tl_pour_pitch = final_pitch - cup_tl_angle

    cup_radius2d = np.linalg.norm([cup_tl_in_axis])
    pivot_in_bowl_tr = Point(
        x=-(cup_radius2d * np.math.cos(cup_tl_pour_pitch) + 0.01),
        z=(cup_radius2d * np.math.sin(cup_tl_pour_pitch) + Z_OFFSET))

    pivot_in_bowl_center = Point(x=radius, z=bowl_h / 2) + pivot_in_bowl_tr
    base_from_pivot = Pose(
        Point(x=axis_in_cup_center_x, z=axis_in_cup_center_z))

    #####

    assert -np.pi <= final_pitch <= initial_pitch
    pitches = [initial_pitch]
    if final_pitch != initial_pitch:
        pitches = list(np.arange(final_pitch, initial_pitch,
                                 np.pi / 16)) + pitches
    cup_path_in_bowl = []
    for pitch in pitches:
        rotate_pivot = Pose(
            euler=Euler(pitch=pitch)
        )  # Can also interpolate directly between start and end quat
        cup_path_in_bowl.append(
            multiply(Pose(point=bowl_center), Pose(pivot_in_bowl_center),
                     rotate_pivot, invert(base_from_pivot),
                     invert(Pose(point=cup_center))))
    return cup_path_in_bowl
Ejemplo n.º 7
0
def get_spoon_grasps(name, body, under=False, grasp_length=0.02):
    # TODO: scale thickness based on size
    thickness = SPOON_THICKNESSES[get_type(name)]
    # Origin is in the center of the spoon's hemisphere head
    # grasp_length depends on the grasp position
    center, extent = approximate_as_prism(body)
    for k in range(1 + under):
        rotate_y = Pose(euler=Euler(pitch=-np.pi / 2 + np.pi * k))
        rotate_z = Pose(euler=Euler(yaw=-np.pi / 2))
        length = (-center + extent / 2)[1] - grasp_length
        translate_x = Pose(point=Point(x=length, y=-thickness / 2.))
        gripper_from_spoon = multiply(translate_x, rotate_z, rotate_y)
        yield gripper_from_spoon
Ejemplo n.º 8
0
def get_contained_beads(body, beads, height_fraction=1.0, top_threshold=0.0):
    #aabb = get_aabb(body)
    center, extent = approximate_as_prism(body, body_pose=get_pose(body))
    lower = center - extent / 2.
    upper = center + extent / 2.
    _, _, z1 = lower
    x2, y2, z2 = upper
    z_upper = z1 + height_fraction * (z2 - z1) + top_threshold
    new_aabb = AABB(lower, np.array([x2, y2, z_upper]))
    #handles = draw_aabb(new_aabb)
    return {
        bead
        for bead in beads if aabb_contains_aabb(get_aabb(bead), new_aabb)
    }
Ejemplo n.º 9
0
def get_placement_surface(body):
    # TODO: check that only has one link
    visual_data = get_visual_data(body, link=BASE_LINK)
    if (len(visual_data) != 1) or (visual_data[0].visualGeometryType != p.GEOM_MESH):
        center, (w, l, h) = approximate_as_prism(body)
        mesh = rectangular_mesh(w, l)
        local_pose = Pose(center + np.array([0, 0, h/2.]))
        #handles = draw_mesh(mesh)
        #wait_for_user()
        return mesh, local_pose
    mesh = read_obj(visual_data[0].meshAssetFileName)
    local_pose = (visual_data[0].localVisualFrame_position,
                  visual_data[0].localVisualFrame_orientation)
    return mesh, local_pose
Ejemplo n.º 10
0
Archivo: push.py Proyecto: lyltc1/LTAMP
def get_push_feature(world, arm, block_name, initial_pose, goal_pos2d):
    block_body = world.get_body(block_name)
    block_reference = get_reference_pose(block_name)
    _, (block_w, block_l, block_h) = approximate_as_prism(block_body, body_pose=block_reference)
    goal_pose = get_end_pose(initial_pose, goal_pos2d)
    difference_initial =  point_from_pose(multiply(invert(initial_pose), goal_pose))

    feature = {
        'arm_name': arm,
        'block_name': block_name,
        'block_type': get_type(block_name),
        'block_width': block_w,
        'block_length': block_l,
        'block_height': block_h,

        'push_yaw': get_yaw(difference_initial),
        'push_distance': get_length(difference_initial)
    }
    return feature
Ejemplo n.º 11
0
Archivo: pour.py Proyecto: lyltc1/LTAMP
def pour_path_from_parameter(world, feature, parameter, cup_yaw=None):
    cup_body = world.get_body(feature['cup_name'])
    #cup_urdf_from_center = get_urdf_from_center(cup_body, reference_quat=get_liquid_quat(feature['cup_name']))
    ref_from_urdf = (unit_point(), get_liquid_quat(feature['cup_name']))
    cup_center_in_ref, _ = approximate_as_prism(cup_body,
                                                body_pose=ref_from_urdf)
    cup_center_in_ref[:
                      2] = 0  # Assumes the xy pour center is specified by the URDF (e.g. for spoons)
    cup_urdf_from_center = multiply(invert(ref_from_urdf),
                                    Pose(point=cup_center_in_ref))

    # TODO: allow some deviation around cup_yaw for spoons
    if cup_yaw is None:
        cup_yaw = random.choice([0, np.pi]) if 'spoon' in feature['cup_name'] \
            else random.uniform(-np.pi, np.pi)
    z_rotate_cup = Pose(euler=Euler(yaw=cup_yaw))

    bowl_from_pivot = get_bowl_from_pivot(world, feature, parameter)
    if RELATIVE_POUR:
        parameter = scale_parameter(feature,
                                    parameter,
                                    RELATIVE_POUR_SCALING,
                                    descale=True)
    base_from_pivot = Pose(
        Point(x=parameter['axis_in_cup_x'], z=parameter['axis_in_cup_z']))

    initial_pitch = 0
    final_pitch = parameter['pitch']
    assert -np.pi <= final_pitch <= initial_pitch
    cup_path_in_bowl = []
    for pitch in list(np.arange(final_pitch, initial_pitch,
                                np.pi / 16)) + [initial_pitch]:
        rotate_pivot = Pose(
            euler=Euler(pitch=pitch)
        )  # Can also interpolate directly between start and end quat
        cup_path_in_bowl.append(
            multiply(bowl_from_pivot, rotate_pivot, invert(base_from_pivot),
                     z_rotate_cup, invert(cup_urdf_from_center)))
    cup_times = constant_velocity_times(cup_path_in_bowl)
    # TODO: check for collisions here?

    return cup_path_in_bowl, cup_times
Ejemplo n.º 12
0
def estimate_spoon_capacity(world, spoon_name, beads, max_beads=100):
    beads = beads[:max_beads]
    if not beads:
        return 0
    bead_radius = np.average(approximate_as_prism(beads[0])) / 2.
    spoon_body = world.get_body(spoon_name)
    spoon_mass = get_mass(spoon_body)
    set_mass(spoon_body, STATIC_MASS)
    set_point(spoon_body, (1, 0, 1))
    set_quat(spoon_body, get_liquid_quat(spoon_name))
    capacity_beads = fill_with_beads(world,
                                     spoon_name,
                                     beads,
                                     reset_contained=True,
                                     fix_outside=False,
                                     height_fraction=1.0,
                                     top_threshold=bead_radius)
    #wait_for_user()
    set_mass(spoon_body, spoon_mass)
    return len(capacity_beads)
Ejemplo n.º 13
0
    def _initialize_environment(self):
        # wall to fridge: 4cm
        # fridge to goal: 1.5cm
        # hitman to range: 3.5cm
        # range to indigo: 3.5cm
        self.environment_poses = read_json(POSES_PATH)
        root_from_world = get_link_pose(self.kitchen, self.world_link)
        for name, world_from_part in self.environment_poses.items():
            if name in ['range']:
                continue
            visual_path = os.path.join(KITCHEN_LEFT_PATH,
                                       '{}.obj'.format(name))
            collision_path = os.path.join(KITCHEN_LEFT_PATH,
                                          '{}_collision.obj'.format(name))
            mesh_path = None
            for path in [collision_path, visual_path]:
                if os.path.exists(path):
                    mesh_path = path
                    break
            if mesh_path is None:
                continue
            body = load_pybullet(mesh_path, fixed_base=True)
            root_from_part = multiply(root_from_world, world_from_part)
            if name in ['axe', 'dishwasher', 'echo', 'fox', 'golf']:
                (pos, quat) = root_from_part
                # TODO: still not totally aligned
                pos = np.array(pos) + np.array([0, -0.035, 0])  # , -0.005])
                root_from_part = (pos, quat)
            self.environment_bodies[name] = body
            set_pose(body, root_from_part)
        # TODO: release bounding box or convex hull
        # TODO: static object nonconvex collisions

        if TABLE_NAME in self.environment_bodies:
            body = self.environment_bodies[TABLE_NAME]
            _, (w, l, _) = approximate_as_prism(body)
            _, _, z = get_point(body)
            new_pose = Pose(Point(TABLE_X + l / 2, -TABLE_Y, z),
                            Euler(yaw=np.pi / 2))
            set_pose(body, new_pose)
Ejemplo n.º 14
0
def visualize_vector_field(learner, bowl_type='blue_bowl', cup_type='red_cup',
                           num=500, delta=False, alpha=0.5):
    print(learner, len(learner.results))
    xx, yy, ww = learner.xx, learner.yy, learner.weights
    learner.hyperparameters = get_parameters(learner.model)
    print(learner.hyperparameters)
    learner.query_type = REJECTION  # BEST | REJECTION | STRADDLE

    world = create_world()
    world.bodies[bowl_type] = load_cup_bowl(bowl_type)
    world.bodies[cup_type] = load_cup_bowl(cup_type)
    feature = get_pour_feature(world, bowl_type, cup_type)
    set_point(world.bodies[cup_type], np.array([0.2, 0, 0]))

    # TODO: visualize training data as well
    # TODO: artificially limit training data to make a low-confidence region
    # TODO: visualize mean and var at the same time using intensity and hue
    print('Feature:', feature)
    with LockRenderer():
        #for parameter in islice(learner.parameter_generator(world, feature, valid=True), num):
        parameters = []
        while len(parameters) < num:
            parameter = learner.sample_parameter()
            # TODO: special color if invalid
            if is_valid_pour(world, feature, parameter):
                parameters.append(parameter)

    center, _ = approximate_as_prism(world.get_body(cup_type))
    bodies = []
    with LockRenderer():
        for parameter in parameters:
            path, _ = pour_path_from_parameter(world, feature, parameter)
            pose = path[0]
            body = create_cylinder(radius=0.001, height=0.02, color=apply_alpha(GREY, alpha))
            set_pose(body, multiply(pose, Pose(point=center)))
            bodies.append(body)

    #train_sizes = inclusive_range(10, 100, 1)
    #train_sizes = inclusive_range(10, 100, 10)
    #train_sizes = inclusive_range(100, 1000, 100)
    #train_sizes = [1000]
    train_sizes = [None]

    # training_poses = []
    # for result in learner.results[:train_sizes[-1]]:
    #     path, _ = pour_path_from_parameter(world, feature, result['parameter'])
    #     pose = path[0]
    #     training_poses.append(pose)

    # TODO: draw the example as well
    scores_per_size = []
    for train_size in train_sizes:
        if train_size is not None:
            learner.xx, learner.yy, learner.weights = xx[:train_size], yy[:train_size],  ww[:train_size]
            learner.retrain()
        X = np.array([learner.func.x_from_feature_parameter(feature, parameter) for parameter in parameters])
        predictions = learner.predict_x(X, noise=False) # Noise is just a constant
        scores_per_size.append([prediction['mean'] for prediction in predictions]) # mean | variance
        #scores_per_size.append([1./np.sqrt(prediction['variance']) for prediction in predictions])
        #scores_per_size.append([prediction['mean'] / np.sqrt(prediction['variance']) for prediction in predictions])
        #plt.hist(scores_per_size[-1])
        #plt.show()
        #scores_per_size.append([scipy.stats.norm.interval(alpha=0.95, loc=prediction['mean'],
        #                                                  scale=np.sqrt(prediction['variance']))[0]
        #                         for prediction in predictions]) # mean | variance
        # score = learner.score(feature, parameter)

    if delta:
        scores_per_size = [np.array(s2) - np.array(s1) for s1, s2 in zip(scores_per_size, scores_per_size[1:])]
        train_sizes = train_sizes[1:]

    all_scores = [score for scores in scores_per_size for score in scores]
    #interval = (min(all_scores), max(all_scores))
    interval = scipy.stats.norm.interval(alpha=0.95, loc=np.mean(all_scores), scale=np.std(all_scores))
    #interval = DEFAULT_INTERVAL
    print('Interval:', interval)
    print('Mean: {:.3f} | Stdev: {:.3f}'.format(np.mean(all_scores), np.std(all_scores)))

    #train_body = create_cylinder(radius=0.002, height=0.02, color=apply_alpha(BLACK, 1.0))
    for i, (train_size, scores) in enumerate(zip(train_sizes, scores_per_size)):
        print('Train size: {} | Average: {:.3f} | Min: {:.3f} | Max: {:.3f}'.format(
            train_size, np.mean(scores), min(scores), max(scores)))
        handles = []
        # TODO: visualize delta
        with LockRenderer():
            #if train_size is None:
            #    train_size = len(learner.results)
            #set_pose(train_body, multiply(training_poses[train_size-1], Pose(point=center)))
            #set_pose(world.bodies[cup_type], training_poses[train_size-1])
            for score, body in zip(scores, bodies):
                score = clip(score, *interval)
                fraction = rescale(score, interval, new_interval=(0, 1))
                color = interpolate_hue(fraction)
                #color = (1 - fraction) * np.array(RED) + fraction * np.array(GREEN) # TODO: convex combination
                set_color(body, apply_alpha(color, alpha))
                #set_pose(world.bodies[cup_type], pose)
                #draw_pose(pose, length=0.05)
                #handles.extend(draw_point(tform_point(pose, center), color=color))
                #extent = np.array([0, 0, 0.01])
                #start = tform_point(pose, center - extent/2)
                #end = tform_point(pose, center + extent/2)
                #handles.append(add_line(start, end, color=color, width=1))
        wait_for_duration(0.5)
        # TODO: test on boundaries
    wait_for_user()