def __init__(self, R: Robot): self.origin = R.get_effector() self.R = R self.w = R.kin_eps
def __init__(self, R: Robot): self.robot = R self.origin = R.get_effector()
def moved_normally(R: Robot, target: Vector3): m = R.move_to(target) col = R.check_collisions() if col: log()['COLLISION'].log('collision detected') return m and not col
def getstate(self): print("MapSize:{}x{}, Parking Number:{}".format( self.Height, self.Width, len(self.parkingdict))) if __name__ == '__main__': from env.robot import Robot vision = Vision() vision.initwindows() vision.initrobot(2) vision.setrobot(1, 1) path = vision.astar(vision.imports, (0, 12)) robot = Robot(0, vision.imports) robot.setpath(path) while not robot.IsArrive: step = robot.move() if tuple(step) == (0, 0): break vision.moverobot(1, step) vision.updatesim(0.5) stop = input('stop Y/n : ') if stop == 'y': vision.destroy() vision.mainloop()
class Planner(object): LENGTHS = [RobotSettings.LINK_1_LENGTH, RobotSettings.LINK_2_LENGTH] # Maximum step size between configurations MAX_STEP = 2.0 MAX_PROXIMITY = 60.0 # magic constant - how close to connect configs X_MAX = WorkspaceSettings.WIDTH Y_MAX = WorkspaceSettings.HEIGHT MAX_ANGLE = 230.0 MIN_ANGLE = -50.0 N = 3 # Number of joints (inc. end effector) TIMEOUT = 30 # seconds SAFE_FACTOR = 1.0 RISKY_FACTOR = 0.6 SWEAT_FACTOR = 0.5 def __init__(self): self.scene = Scene() self.robot = Robot() self.obstacles = [] self.path = Path() self.sampler = Sampler(self.LENGTHS) self.validator = Validator() self.configs = [] self.path_found = False self.super_path = SuperPath() self.safety = True self.all_paths_found = False def received_scene_string(self, scene_string): """ Convert the string to a scene, process the scene, and return a path string. """ self.__init__() self.scene.decode(scene_string) print("Can Move: ", self.scene.moveableBlocks) self.scene.render() if len(self.scene.pucks) > len(self.scene.holes): print("{0} pucks, {1} goals - can't do it".format(len(self.scene.pucks), len(self.scene.holes))) exit() self.scene.print_scene() self.set_obstacles(self.scene, pucks=True) if len(self.scene.moveableBlocks) > 0: print("ATTEMPTING TO MOVE A BLOCK") self.move_block() # return self.super_path.to_string() all_paths, costs = self.find_all_paths() goal_indices, goal_costs = self.find_puck_goal_mapping(costs) for i in range(len(goal_indices)): self.super_path.paths.append(all_paths[i][goal_indices[i]]) # Show path(s) in simulator sim_paths = [ np.array([list(cfg.positions[-1]) for cfg in path.path]) for path in self.super_path.paths ] # print("Planner: sim paths are \n", sim_paths) self.scene.render(paths=sim_paths) return self.super_path.to_string() def move_block(self): push_behind = 50 push_distance = 50 max_push_distance = 150 # Plan path with movable block removed until path found for block in self.scene.moveableBlocks: self.set_obstacles(self.scene, pucks=True) if block.y < 0: continue print("Attempting to remove block at ", block.x, block.y) print("Length before removal = ", len(self.obstacles)) self.obstacles.remove(block.shape) print("Length after removal = ", len(self.obstacles)) if len(self.scene.moveableBlocks) == 1: all_paths, costs = self.find_all_paths(impatient=False) else: all_paths, costs = self.find_all_paths(impatient=True) if self.all_paths_found: self.configs = [] block_to_move = block x = block.x y = block.y if block.blockType == BlockSettings.CYLINDER: push_behind = 40 if block.blockType == BlockSettings.CUBE: push_behind = 45 elif block.blockType == BlockSettings.PRISM: push_behind = 65 break self.configs = [] if not self.all_paths_found: print("Error: Couldn't find a path for block pushing") exit() # Goals are obstacles when it comes to pushing self.set_obstacles(self.scene, pucks=True, goals=True) self.obstacles.remove(block_to_move.shape) min_d1 = 1000000 min_d2 = 1000000 nearest_block1 = None nearest_block2 = None for block in self.scene.blocks: if block.y < 0: continue d = gm.distance((block.x, block.y), (x, y)) if d < 0.5: # THIS IS THE BLOCK THATS MOVING. 0.5 IS ARBITRARY continue if d < min_d1: min_d2 = min_d1 min_d1 = d nearest_block2 = nearest_block1 nearest_block1 = (block.x, block.y) elif d < min_d2: min_d2 = d nearest_block2 = (block.x, block.y) # Check if block is closer to boundary than one of the nearest ww = WorkspaceSettings.WIDTH wh = WorkspaceSettings.HEIGHT bottom = gm.Line2D((-ww / 2, 0.0), (ww / 2, 0.0)) right = gm.Line2D((ww / 2, 0.0), (ww / 2, wh)) top = gm.Line2D((ww / 2, wh), (-ww / 2, wh)) left = gm.Line2D((-ww / 2, wh), (-ww / 2, 0.0)) boundaries = [bottom, right, top, left] # Make the boundary point one of the nearest if so min_db = 1000000 for boundary in boundaries: p = boundary.p_point((x, y)) d = boundary.p_distance((x, y)) if d < min_d1: min_d2 = min_d1 min_d1 = d nearest_block2 = nearest_block1 nearest_block1 = p elif d < min_d2: min_d2 = d nearest_block2 = p if nearest_block1 is None or nearest_block2 is None: # Maybe there were not enought blocks or something? # This shouldn't happen, but if it does it will cause problems print("Planner: Error finding nearest blocks in moving blocks") return print("Found Nearest Blocks: ", nearest_block1, nearest_block2) nearest_block_line = gm.Line2D(nearest_block1, nearest_block2) m1, c1 = nearest_block_line.find_normal_at_point((x, y)) print("Nearest block line: m,c: ", m1, c1) # Direction of push theta = math.atan2(m1, 1.0) print("Theta is ", theta) print("x,y is :", x, y) start_point = gm.point_from_polar((x, y), push_behind, theta + np.pi) start_point = (start_point[0], start_point[1] - 5.0) print("Start point is: ", start_point) start_cfg = self.config_from_point(start_point) max_point = gm.point_from_polar((x, y), max_push_distance, theta) max_cfg = self.config_from_point(max_point) # Move forward (~ along path) # Check each direction until not in collision # TODO: intersect functions for prism and cube nearest_collision = self.step_to_block(start_cfg, max_cfg) if nearest_collision is None: push_distance = max_push_distance else: push_distance = gm.distance((x, y), nearest_collision) print("Nearest collision for first direction: ", nearest_collision) # See if opposite direction is better print("Checking opposite push direction") start_point2 = gm.point_from_polar((x, y), push_behind, theta) start_point2 = (start_point2[0], start_point2[1] - 5.0) start_cfg2 = self.config_from_point(start_point2) max_point2 = gm.point_from_polar((x, y), max_push_distance, theta + np.pi) max_cfg2 = self.config_from_point(max_point) nearest_collision2 = self.step_to_block(start_cfg2, max_cfg2) print("Nearest collision for second direction: ", nearest_collision2) if nearest_collision2 is None: if nearest_collision is not None: # Opposite direction does not collide -> better theta += np.pi start_point = start_point2 start_cfg = start_cfg2 push_distance = max_push_distance # Both None -> go with first option elif nearest_collision is not None: # Both have collision -> choose longest available push push_distance2 = gm.distance((x, y), nearest_collision2) if push_distance2 > push_distance: print("Chose opposite direction") # Direction 2 is better theta += np.pi start_point = start_point2 start_cfg = start_cfg2 push_distance = push_distance2 # Else no change, go with direction 1 end_point = gm.point_from_polar((x, y), push_distance - 20, theta) end_point = (end_point[0], end_point[1] - 5.0) print("End point is: ", end_point) end_cfg = self.config_from_point(end_point) path = Path() path.path.append(start_cfg) path.path.append(end_cfg) path.movingPath = True self.super_path.paths.append(path) # Update block position after push (inaccurate) self.set_obstacles(self.scene, pucks=True) self.obstacles.remove(block_to_move.shape) block_to_move.x = end_point[0] block_to_move.y = end_point[1] block_to_move.find_shape() print("Adding back the moved block: ", block_to_move.shape) # Remove goals for path planning self.obstacles.append(block_to_move.shape) def config_from_point(self, pt): angles = list(self.robot.inverse_kinematics(pt[0], pt[1], None, 0.0)) coordinates = self.robot.forward_kinematics(angles[0], angles[1], angles[2], all_joints=True) return RobotConfig(coordinates[:-1], angles[:-1]) def set_obstacles(self, scene, pucks=False, goals=False): """ Retrieves the obstacles in the environment based on the scene data. :param scene: the scene representing the state of the workspace. :return: list<Shape2D> """ self.obstacles = [block.shape for block in scene.blocks] if pucks: self.obstacles.extend([puck.shape for puck in self.scene.pucks]) if goals: self.obstacles.extend([hole.shape for hole in self.scene.holes]) # print("Planner: scene is:\n", scene.print_scene()) def find_all_paths(self, impatient=False): """ Find all the paths from pucks to holes """ # TODO handle invalid end states with super path # TODO iterate through all root-goal combos after each PRM build print("\nPlanner: planning safely ({0}x puck enlargement)" .format(2 * self.SAFE_FACTOR)) if impatient: timeout = self.TIMEOUT / 2 else: timeout = self.TIMEOUT # Build and search roadmap with random samples until all paths found # or we run out of time t0 = time.time() all_paths = [[None for h in range(len(self.scene.holes))] for p in range(len(self.scene.pucks))] costs = [[None for h in range(len(self.scene.holes))] for p in range(len(self.scene.pucks))] self.all_paths_found = False sample_size = 2 while time.time() - t0 < timeout and not self.all_paths_found: print("\nPlanner: building roadmap with {0} samples" .format(sample_size)) if sample_size == 1024: # print("Planner: visualising") # self.visualise_graph() self.validator.set_enlarge_factor(self.RISKY_FACTOR) print("\nPlanner: living dangerously " \ "({0}x puck enlargement)".format(2 * self.RISKY_FACTOR)) self.safety = False elif sample_size == 4096 and len(self.scene.moveableBlocks) == 0: self.validator.set_enlarge_factor(self.SWEAT_FACTOR) print("\nPlanner: start sweating ({0}x puck enlargement)" .format(2 * self.SWEAT_FACTOR)) self.build_road_map(sample_size) for root_index in range(len(self.scene.pucks)): # Reset to block obstacles only self.obstacles.remove(self.scene.pucks[root_index].shape) for goal_index in range(len(self.scene.holes)): if all_paths[root_index][goal_index] is not None: # Path already found; use that continue self.path = Path() path_possible = self.do_query(root_index, goal_index) if not path_possible: continue puck = self.scene.pucks[root_index] hole = self.scene.holes[goal_index] self.path.deltaTheta = puck.theta - hole.theta # print("DELTA_ANGLE_IS: ", self.path.deltaTheta) if self.path_found: all_paths[root_index][goal_index] = self.path costs[root_index][goal_index] = self.path.cost self.path_found = False self.obstacles.append(self.scene.pucks[root_index].shape) if self.find_puck_goal_mapping(costs) is not None: self.all_paths_found = True # Extend the roadmap in the next iteration if paths were not found sample_size *= 2 if time.time() - t0 > timeout: # TODO try again print("Planner: timeout at {0}s\n".format(time.time() - t0)) return all_paths, costs if self.all_paths_found: print("Planner: found paths in {0}s\n".format(time.time() - t0)) return all_paths, costs def do_query(self, root_index, goal_index): """ Search for a path between the specified root and goal via the roadmap :return: True if a path was found """ self.validator.set_enlarge_factor(0.5) print("Planner: finding path from puck {0} to goal {1}" .format(root_index, goal_index)) root = self.get_end_state(0, index=root_index) goal = self.get_end_state(1, index=goal_index) if not self.validator.is_valid_sample(root, self.obstacles, debug=False) or \ not self.validator.is_valid_sample(goal, self.obstacles, debug=False): print("Planner: ERROR: invalid end states") print("Valid root: ", self.validator.is_valid_sample(root, self.obstacles)) print("Valid goal: ", self.validator.is_valid_sample(goal, self.obstacles)) exit() if self.safety: self.validator.set_enlarge_factor(self.SAFE_FACTOR) else: self.validator.set_enlarge_factor(self.RISKY_FACTOR) # Try direct path direct_connection = self.make_steps(root, goal, check=True)[0] if direct_connection: self.path.set_path([root, goal]) self.path_found = True self.path.cost = gm.distance(root.positions[-1], goal.positions[-1]) print("Planner: goal found directly") return True # Find vertices nearest initial and goal configs connected_root = self.connect_configs(root) connected_goal = self.connect_configs(goal) # Check if root and goal were connected if not (connected_root and connected_goal): # Solution not possible return False # Initial and goal states are now in the roadmap # Search for a path ass = AStarSearch(root, goal) print("Planner: searching") ass.search() if ass.goal_found: # print("Planner: visualising") # self.visualise_graph() # Record solution path = ass.path full_path = [] while len(path) > 1: cfg_0 = path.pop(0) # Uncomment below 4 lines to display/calculate curved paths # cfg_m = path[0] # steps = self.make_steps(cfg_0, cfg_m, check=False)[1] # steps.pop() # full_path += steps full_path.append(cfg_0) full_path.append(goal) self.path.set_path(full_path) self.path.cost = ass.total_cost self.path_found = True # Remove this root and goal from the roadmap, ready for the next for cfg in root.connectors: cfg.connectors.pop(root, False) for cfg in goal.connectors: cfg.connectors.pop(goal, False) return True return False def build_road_map(self, sample_size): """ Add samples to the roadmap """ while len(self.configs) < sample_size: self.sample_uniform() # print("Planner: Finished sampling, attempting to connect configs") self.connect_configs() def sample_uniform(self): """ Sample a configuration uniformly at random. :return: True if the configuration was connected to any others """ sample = self.sampler.sample_cart(self.N, 0.0, 0.0, self.X_MAX, self.Y_MAX, self.MIN_ANGLE, self.MAX_ANGLE) cfg = RobotConfig(sample[0], sample[1]) if self.validator.is_valid_sample(cfg, self.obstacles): self.configs.append(cfg) def connect_configs(self, cfg_0=None): """ Connect a new configuration to a maximum of k others in the state graph, if they are within a certain distance and there is a collision-free path between them. :param cfg_0: The new configuration to connect to :return: True if any connections were made """ num_connections = 0 if len(self.configs) == 0: return False elif len(self.configs) < 5: n_neighbors = len(self.configs) else: n_neighbors = 5 # Make array of end effector positions # print("Planner: Building points") points = np.array([cfg.positions[-1] for cfg in self.configs]) # print("Planner: Finding NearestNeighbors") neigh = NearestNeighbors(n_neighbors=n_neighbors, radius=self.MAX_PROXIMITY) # print("Planner: Fitting points") neigh.fit(points) # distances, indices = neigh.radius_neighbors([cfg_0.positions[-1]]) if cfg_0 is not None: distances, indices = neigh.kneighbors([cfg_0.positions[-1]]) for i in indices[0]: cfg_m = self.configs[i] # if cfg_m not in cfg_0.connectors.keys(): num_connections += self.make_steps(cfg_0, cfg_m)[0] return num_connections > 0 else: distances, indices = neigh.kneighbors(points) for i in indices: cfg_0 = self.configs[i[0]] for j in i[1:]: cfg_m = self.configs[j] if cfg_m not in cfg_0.connectors.keys(): self.make_steps(cfg_0, cfg_m) return False def make_steps(self, cfg_0, cfg_m, check=True): """ Interpolate a straight-line path between configurations in C-space. :param cfg_0: the initial configuration :param cfg_m: the final configuration :param check: check if the path is collision free if True :return: a tuple; the first element is 1 if a connection is made and 0 otherwise; the second element is the interpolated configurations from the initial to the final state inclusive. """ connection = 0 # 1 if valid connection is made steps = [cfg_0, cfg_m] end_loop = False max_step = 1000.0 # max of all iterations while True: old_steps = list(steps) current_max_step = 0 # max of current iteration end_index = len(steps) - 1 for i in range(end_index): # Interpolate the midpoint configuration cfg_1 = old_steps[i] cfg_2 = old_steps[i + 1] cfg_nxt = self.midpoint(cfg_1, cfg_2) steps.insert(2 * i + 1, cfg_nxt) i_max_step = gm.distance(cfg_1.positions[-1], cfg_nxt.positions[-1]) if i_max_step > current_max_step: current_max_step = i_max_step if check and (self.validator.has_collision(cfg_nxt, self.obstacles) or not self.validator.fits_bounds(cfg_nxt)): end_loop = True break if current_max_step < max_step: max_step = current_max_step if max_step < self.MAX_STEP: # No collision and primitive step size has been reached if check: # Connect vertices in state graph jump = PathJump(cfg_0, cfg_m) cfg_0.add_connector(cfg_m, jump) cfg_m.add_connector(cfg_0, jump) connection = 1 break if end_loop: break return connection, steps def step_to_block(self, cfg_0, cfg_m): steps = [cfg_0, cfg_m] p0 = cfg_0.positions[-1] pm = cfg_m.positions[-1] d = gm.distance(p0, pm) # print(d) # print(p0, pm) theta = math.atan2(float(pm[1] - p0[1]), (pm[0] - p0[0])) # print(theta) num_steps = int(math.ceil(d * 1.0 / self.MAX_STEP)) # print(num_steps) while len(steps) - 1 < num_steps: for i in range(num_steps - 1): cfg_1 = steps[i] cfg_nxt = self.config_from_point(gm.point_from_polar( cfg_1.positions[-1], self.MAX_STEP, theta)) steps.insert(i + 1, cfg_nxt) if (not self.validator.fits_bounds(cfg_nxt)) \ or self.validator.has_collision( cfg_nxt, self.obstacles): print(i) print("hit block or out of bounds") print(cfg_nxt.positions[-1]) return cfg_nxt.positions[-1] # print(steps) # print(len(steps)) return None def midpoint(self, cfg_0, cfg_1): coordinates = [] angles = [] # p00 = cfg_0.get_positions()[0] # p10 = cfg_1.get_positions()[0] # p_mid_0 = ((p00[0] + p10[0])*1.0/2, (p00[1] + p10[1])*1.0/2) # Assume same base position coordinates.append(cfg_0.get_position(0)) thetas_0 = cfg_0.get_angles() thetas_1 = cfg_1.get_angles() delta_thetas = self.delta_thetas(thetas_0, thetas_1) phi = 0 for i in range(len(thetas_0)): p_ref = coordinates[i] # Rotation = preceding angle for mid config + this angle for # previous config + change in this angle theta_mid = thetas_0[i] + delta_thetas[i] * 1.0 / 2 angles.append(theta_mid) phi += theta_mid p_mid = (p_ref[0] + self.LENGTHS[i] * math.cos(math.radians(phi)), p_ref[1] + self.LENGTHS[i] * math.sin(math.radians(phi))) coordinates.append(p_mid) return RobotConfig(coordinates, angles) def delta_thetas(self, thetas_0, thetas_1): delta_thetas = [] for i in range(self.N - 1): delta_theta = thetas_1[i] - thetas_0[i] if i == 0: delta_theta = self.normalise_angle(delta_theta) delta_thetas.append(delta_theta) return delta_thetas @staticmethod def normalise_angle(angle): """ Normalises an angle to the range (-180, 180] :param angle: the angle to normalise :return: the normalised angle """ while angle <= -180.0: angle += 2 * 180.0 while angle > 180.0: angle -= 2 * 180.0 return angle def get_end_state(self, state, index=0): """ Get an end state where state = 0 means puck state = 1 means goal if there are more than one puck or goal, index can be used to get the state of the i'th puck or goal """ if state == 0: p = self.scene.pucks[index].shape elif state == 1: p = self.scene.holes[index].shape else: return None return self.config_from_point(p.centre) def visualise_graph(self): plt.ylim((-200, 400)) plt.xlim((-400, 400)) plotted = [] for config in self.configs: end_pos1 = config.positions[-1] for config2 in config.get_connectors().keys(): end_pos2 = config2.positions[-1] if end_pos2 not in plotted: connection = np.array((end_pos1, end_pos2)) plt.plot(connection[:, 0], connection[:, 1], color='black', marker='8', linewidth=0.5) plotted.append(end_pos1) plt.show() @staticmethod def find_puck_goal_mapping(costs): print("Planner: path costs:\n", costs) perms = permutations(range(len(costs[0])), len(costs)) endCosts = [] permList = [] permCostList = [] for i in perms: permList.append(i) permCosts = [] for j in range(len(costs)): permCosts.append(costs[j][i[j]]) if None in permCosts: endCosts.append(None) else: endCosts.append(sum(permCosts)) permCostList.append(permCosts) minIndex = None minValue = sys.float_info.max for x in range(len(endCosts)): if endCosts[x] is None: continue if endCosts[x] < minValue: minIndex = x minValue = endCosts[x] if minIndex is None: return None return permList[minIndex], permCostList[minIndex]
class Simulator(object): def __init__(self): self.fig = plt.figure() self.ax = self.fig.add_subplot(111) # self.fig, self.ax = plt.subplots() self.robot = Robot() self.obstacles = [] self.goals = [] self.pucks = [] self.endPoints = None self.elbowPoints = None self.pathPoints = None self.paths = [] self.pathColour = 'black' self.goalColour = 'cyan' self.puckColour = 'gray' self.obstacleColour = 'red' self.workspaceFeatures = [] self.workspaceColour = 'green' self.armColour = 'black' # plt.ion() def add_goal(self, xy, radius): circle = Circle(xy, radius) self.goals.append(circle) def add_puck(self, xy, radius): circle = Circle(xy, radius) self.pucks.append(circle) def add_obstacle_polygon(self, points): self.obstacles.append(Polygon(points)) def add_obstacle_circle(self, xy, radius): circle = Circle(xy, radius) self.obstacles.append(circle) def set_axis_limits(self, x_limits, y_limits): plt.ylim(y_limits) plt.xlim(x_limits) def add_workspace(self, width, height): xleft = 0.0 - width/2 xright = 0.0 + width/2 ylower = 0.0 yupper = height points = np.array([[xleft, ylower], [xright, ylower], [xright, yupper], [xleft, yupper]]) self.workspaceFeatures.append(Polygon(points)) def add_arm(self, theta1, theta2, theta3=0): self.elbowPoints = self.robot.get_elbow_position(theta1) self.endPoints = self.robot.forward_kinematics(theta1, theta2, theta3)[0:2] def add_path(self, path): self.paths.append(path) def set_scene(self, scene=None): for puck in scene.pucks: self.add_puck(puck.shape.centre, puck.shape.radius) for hole in scene.holes: self.add_goal(hole.shape.centre, hole.shape.radius) for block in scene.blocks: shape = block.shape if shape is None: continue if shape.type == "circle": self.add_obstacle_circle(shape.centre, shape.radius) else: vertices = np.array([list(v) for v in shape.vertices]) self.add_obstacle_polygon(vertices) def show_figure(self): # plt.figure(1) # plt.clf() # plt.close() # self.set_axis_limits((-400.0, 400.0), (-200.0, 400.0)) # self.add_workspace(WorkspaceSettings.WIDTH, WorkspaceSettings.HEIGHT) # self.set_scene() # self.add_arm(180.0, -90.0, 0.0) # if len(self.paths) == 0: # plt.close() p = PatchCollection(self.workspaceFeatures, alpha=0.1, color=self.workspaceColour, edgecolor='black') self.ax.add_collection(p) p = PatchCollection(self.obstacles, alpha=0.9, color=self.obstacleColour, edgecolor='black') self.ax.add_collection(p) p = PatchCollection(self.goals, alpha=0.9, color=self.goalColour, edgecolor='black') self.ax.add_collection(p) p = PatchCollection(self.pucks, alpha=0.9, color=self.puckColour, edgecolor='black') self.ax.add_collection(p) if len(self.paths) > 0: i = 0 for path in self.paths: plt.plot(path[:, 0], path[:, 1], color=COLORS[i], solid_capstyle='round', alpha=0.2, linewidth=BlockSettings.PUCK_DIAMETER/2) plt.plot(path[:, 0], path[:, 1], color=self.armColour, linestyle='--', linewidth=2) i += 1 if self.elbowPoints is not None: plt.plot([0, self.elbowPoints[0], self.endPoints[0]], [-150, self.elbowPoints[1], self.endPoints[1]], color=self.armColour, linestyle='-', linewidth=5) # plt.pause(0.00001) # self.fig.canvas.draw() # plt.draw() # plt.pause(1) plt.show()