Esempio n. 1
0
    def generate_frame_variant(self, frame, options=None):
        """....

        Parameters
        ----------
        frame : :class:`compas.geometry.Frame`
            TODO

        Yield
        -----
        frame
            frame variantion
        """
        # overwrite if options is provided in the function call
        options = options or self._options

        # * half range
        delta_roll = is_valid_option(options, 'delta_roll', 0.)
        delta_pitch = is_valid_option(options, 'delta_pitch', 0.)
        delta_yaw = is_valid_option(options, 'delta_yaw', 0.)

        # * discretization
        roll_sample_size = is_valid_option(options, 'roll_sample_size', 1)
        pitch_sample_size = is_valid_option(options, 'pitch_sample_size', 1)
        yaw_sample_size = is_valid_option(options, 'yaw_sample_size', 1)

        roll_gen = np.linspace(-delta_roll, delta_roll, num=roll_sample_size)
        pitch_gen = np.linspace(-delta_pitch, delta_pitch, num=pitch_sample_size)
        yaw_gen = np.linspace(-delta_yaw, delta_yaw, num=yaw_sample_size)
        pose = pose_from_frame(frame)
        # a finite generator
        for roll, pitch, yaw in product(roll_gen, pitch_gen, yaw_gen):
            yield frame_from_pose(multiply(pose, Pose(euler=Euler(roll=roll, pitch=pitch, yaw=yaw))))
Esempio n. 2
0
    def plan_motion(self, robot, goal_constraints, start_configuration=None, group=None, options=None):
        """Calculates a motion path.

        Parameters
        ----------
        robot : :class:`compas_fab.robots.Robot`
            The robot instance for which the motion path is being calculated.
        goal_constraints: list of :class:`compas_fab.robots.Constraint`
            The goal to be achieved, defined in a set of constraints.
            Constraints can be very specific, for example defining value domains
            for each joint, such that the goal configuration is included,
            or defining a volume in space, to which a specific robot link (e.g.
            the end-effector) is required to move to.
        start_configuration: :class:`compas_fab.robots.Configuration`, optional
            The robot's full configuration, i.e. values for all configurable
            joints of the entire robot, at the starting position.
        group: str, optional
            The name of the group to plan for.
        options : dict, optional
            Dictionary containing kwargs for arguments specific to
            the client being queried.
            - ``"avoid_collisions"``: (:obj:`bool`, optional)
              Whether or not to avoid collisions. Defaults to ``True``.

            - ``"max_step"``: (:obj:`float`, optional) The approximate distance between the
              calculated points. (Defined in the robot's units.) Defaults to ``0.01``.
            - ``"jump_threshold"``: (:obj:`float`, optional)
              The maximum allowed distance of joint positions between consecutive
              points. If the distance is found to be above this threshold, the
              path computation fails. It must be specified in relation to max_step.
              If this threshold is ``0``, 'jumps' might occur, resulting in an invalid
              cartesian path. Defaults to :math:`\\pi / 2`.

        Returns
        -------
        :class:`compas_fab.robots.JointTrajectory`
            The calculated trajectory.
        """
        robot_uid = robot.attributes['pybullet_uid']

        # * parse options
        verbose = is_valid_option(options, 'verbose', False)
        diagnosis = options.get('diagnosis', False)
        custom_limits = options.get('custom_limits') or {}
        resolutions = options.get('resolutions') or 0.1
        weights = options.get('weights') or None
        rrt_restarts = options.get('rrt_restarts', 2)
        rrt_iterations = options.get('rrt_iterations', 20)
        smooth_iterations = options.get('smooth_iterations', 20)
        # TODO: auto compute joint weight
        # print('plan motion options: ', options)

        # * convert link/joint names to pybullet indices
        joint_names = robot.get_configurable_joint_names(group=group)
        ik_joints = joints_from_names(robot_uid, joint_names)
        joint_types = robot.get_joint_types_by_names(joint_names)
        pb_custom_limits = get_custom_limits(robot_uid, ik_joints,
            custom_limits={joint_from_name(robot_uid, jn) : lims for jn, lims in custom_limits.items()})
        # print('pb custom limits: ', list(pb_custom_limits))

        with WorldSaver():
            if start_configuration is not None:
                # * set to start conf
                self.client.set_robot_configuration(robot, start_configuration)
            sample_fn = get_sample_fn(robot_uid, ik_joints, custom_limits=pb_custom_limits)
            distance_fn = get_distance_fn(robot_uid, ik_joints, weights=weights)
            extend_fn = get_extend_fn(robot_uid, ik_joints, resolutions=resolutions)
            options['robot'] = robot
            collision_fn = PyChoreoConfigurationCollisionChecker(self.client)._get_collision_fn(robot, joint_names, options=options)

            start_conf = get_joint_positions(robot_uid, ik_joints)
            end_conf = self._joint_values_from_joint_constraints(joint_names, goal_constraints)
            assert len(ik_joints) == len(end_conf)

            if not check_initial_end(start_conf, end_conf, collision_fn, diagnosis=diagnosis):
                return None
            path = birrt(start_conf, end_conf, distance_fn, sample_fn, extend_fn, collision_fn,
                restarts=rrt_restarts, iterations=rrt_iterations, smooth=smooth_iterations)
            #return plan_lazy_prm(start_conf, end_conf, sample_fn, extend_fn, collision_fn)

        if path is None:
            # TODO use LOG
            if verbose:
                cprint('No free motion found!', 'red')
            return None
        else:
            jt_traj_pts = []
            for i, conf in enumerate(path):
                # c_conf = Configuration(values=conf, types=joint_types, joint_names=joint_names)
                jt_traj_pt = JointTrajectoryPoint(values=conf, types=joint_types, time_from_start=Duration(i*1,0))
                # TODO why don't we have a `joint_names` input for JointTrajectoryPoint?
                # https://github.com/compas-dev/compas_fab/blob/master/src/compas_fab/robots/trajectory.py#L64
                jt_traj_pt.joint_names = joint_names
                jt_traj_pts.append(jt_traj_pt)
            trajectory = JointTrajectory(trajectory_points=jt_traj_pts,
                joint_names=joint_names, start_configuration=jt_traj_pts[0], fraction=1.0)
            return trajectory
Esempio n. 3
0
    def inverse_kinematics(self,
                           robot,
                           frame_WCF,
                           start_configuration=None,
                           group=None,
                           options=None):
        """Calculate the robot's inverse kinematic for a given frame.

        Parameters
        ----------
        robot : :class:`compas_fab.robots.Robot`
            The robot instance for which inverse kinematics is being calculated.
        frame_WCF: :class:`compas.geometry.Frame`
            The frame to calculate the inverse for.
        start_configuration: :class:`compas_fab.robots.Configuration`, optional
            If passed, the inverse will be calculated such that the calculated
            joint positions differ the least from the start_configuration.
            Defaults to the init configuration.
        group: str, optional
            The planning group used for calculation. Defaults to the robot's
            main planning group.
        options: dict, optional
            Dictionary containing the following key-value pairs:
            - ``"base_link"``: (:obj:`str`) Name of the base link.
            - ``"avoid_collisions"``: (:obj:`bool`, optional) Whether or not to avoid collisions.
              Defaults to `True`.
            - ``"constraints"``: (:obj:`list` of :class:`compas_fab.robots.Constraint`, optional)
              A set of constraints that the request must obey. Defaults to `None`.
            - ``"attempts"``: (:obj:`int`, optional) The maximum number of inverse kinematic attempts.
              Defaults to `8`.
            - ``"attached_collision_meshes"``: (:obj:`list` of :class:`compas_fab.robots.AttachedCollisionMesh`, optional)
              Defaults to `None`.
            - ``"return_all"``: (bool, optional) return a list of all computed ik solutions
              Defaults to False
        Raises
        ------
        compas_fab.backends.exceptions.BackendError
            If no configuration can be found.
        Returns
        -------
        :class:`compas_fab.robots.Configuration`
            The planning group's configuration.
        """
        max_iterations = is_valid_option(options, 'attempts', 8)
        avoid_collisions = is_valid_option(options, 'avoid_collisions', True)
        return_all = is_valid_option(options, 'return_all', False)
        custom_limits = options.get('custom_limits') or {}
        # return_closest_to_start = is_valid_option(options, 'return_closest_to_start', False)
        # cull = is_valid_option(options, 'cull', True)

        robot_uid = robot.attributes['pybullet_uid']
        ik_joint_names = robot.get_configurable_joint_names(group=group)
        ik_joints = joints_from_names(robot_uid, ik_joint_names)
        tool_link_name = robot.get_end_effector_link_name(group=group)
        tool_link = link_from_name(robot_uid, tool_link_name)
        pb_custom_limits = get_custom_limits(
            robot_uid,
            ik_joints,
            custom_limits={
                joint_from_name(robot_uid, jn): lims
                for jn, lims in custom_limits.items()
            })

        target_pose = pose_from_frame(frame_WCF)
        with WorldSaver():
            if start_configuration is not None:
                start_conf_vals = start_configuration.values
                set_joint_positions(robot_uid, ik_joints, start_conf_vals)
            # else:
            #     sample_fn = get_sample_fn(robot_uid, ik_joints)
            #     start_conf_vals = sample_fn()

            # if group not in self.client.planner.ik_fn_from_group:
            # use default ik fn
            conf_vals = self._compute_ik(robot_uid,
                                         ik_joints,
                                         tool_link,
                                         target_pose,
                                         max_iterations,
                                         custom_limits=pb_custom_limits)

            joint_types = robot.get_joint_types_by_names(ik_joint_names)
            configurations = [Configuration(values=conf_val, types=joint_types, joint_names=ik_joint_names) \
                for conf_val in conf_vals if conf_val is not None]
            # else:
            #     # qs = client.inverse_kinematics(frame_WCF, group=move_group)
            #     configurations = self.client.planner.ik_fn_from_group[group](frame_WCF, group=group, options=options)

            if avoid_collisions:
                configurations = [
                    conf for conf in configurations
                    if not self.client.check_collisions(
                        robot, conf, options=options)
                ]

        if return_all:
            return configurations
        else:
            return configurations[0] if len(configurations) > 0 else None
    def plan_cartesian_motion(self,
                              robot,
                              frames_WCF,
                              start_configuration=None,
                              group=None,
                              options=None):
        """Calculates a cartesian motion path (linear in tool space).

        Parameters
        ----------
        robot : :class:`compas_fab.robots.Robot`
            The robot instance for which the motion path is being calculated.
        frames_WCF: list of :class:`compas.geometry.Frame`
            The frames through which the path is defined.
        start_configuration: :class:`Configuration`, optional
            The robot's full configuration, i.e. values for all configurable
            joints of the entire robot, at the starting position.
        group: str, optional
            The planning group used for calculation.
        options: dict, optional
            Dictionary containing kwargs for arguments specific to
            the client being queried.

            - ``"diagnosis"``: (:obj:`bool`, optional)
              . Defaults to ``False``.

            - ``"avoid_collisions"``: (:obj:`bool`, optional)
              Whether or not to avoid collisions. Defaults to ``True``.

            - ``"planner_id"``: (:obj:`str`)
              The name of the algorithm used for path planning.
              Defaults to ``'IterativeIK'``.
              Available planners: ``'IterativeIK', 'LadderGraph'``

            - ``"max_step"``: (:obj:`float`, optional) The approximate distance between the
              calculated points. (Defined in the robot's units.) Defaults to ``0.01``.
            - ``"jump_threshold"``: (:obj:`float`, optional)
              The maximum allowed distance of joint positions between consecutive
              points. If the distance is found to be above this threshold, the
              path computation fails. It must be specified in relation to max_step.
              If this threshold is ``0``, 'jumps' might occur, resulting in an invalid
              cartesian path.
              Defaults to :math:`\\pi / 6` for revolute or continuous joints, 0.1 for prismatic joints.

              # TODO: JointConstraint

        Returns
        -------
        :class:`compas_fab.robots.JointTrajectory`
            The calculated trajectory.
        """
        robot_uid = robot.attributes['pybullet_uid']

        # * convert link/joint names to pybullet indices
        base_link_name = robot.get_base_link_name(group=group)
        tool_link_name = robot.get_end_effector_link_name(group=group)
        tool_link = link_from_name(robot_uid, tool_link_name)
        base_link = link_from_name(robot_uid, base_link_name)

        joint_names = robot.get_configurable_joint_names(group=group)
        joint_types = robot.get_joint_types_by_names(joint_names)
        ik_joints = joints_from_names(robot_uid, joint_names)

        # * parse options
        verbose = options.get('verbose', True)
        diagnosis = options.get('diagnosis', False)
        avoid_collisions = options.get('avoid_collisions', True)
        pos_step_size = options.get('max_step', 0.01)
        planner_id = is_valid_option(options, 'planner_id', 'IterativeIK')
        jump_threshold = is_valid_option(options, 'jump_threshold', {jt_name : math.pi/6 \
            if jt_type in [Joint.REVOLUTE, Joint.CONTINUOUS] else 0.1 \
            for jt_name, jt_type in zip(joint_names, joint_types)})
        jump_threshold_from_joint = {
            joint_from_name(robot_uid, jt_name): j_diff
            for jt_name, j_diff in jump_threshold.items()
        }
        # * iterative IK options
        pos_tolerance = is_valid_option(options, 'pos_tolerance', 1e-3)
        ori_tolerance = is_valid_option(options, 'ori_tolerance', 1e-3 * np.pi)
        # * ladder graph options
        frame_variant_gen = is_valid_option(options, 'frame_variant_generator',
                                            None)
        ik_function = is_valid_option(options, 'ik_function', None)

        # * convert to poses and do workspace linear interpolation
        given_poses = [pose_from_frame(frame_WCF) for frame_WCF in frames_WCF]
        ee_poses = []
        for p1, p2 in zip(given_poses[:-1], given_poses[1:]):
            c_interp_poses = list(
                interpolate_poses(p1, p2, pos_step_size=pos_step_size))
            ee_poses.extend(c_interp_poses)

        # * build collision fn
        attachments = values_as_list(self.client.pychoreo_attachments)
        collision_fn = PyChoreoConfigurationCollisionChecker(
            self.client)._get_collision_fn(robot, joint_names, options=options)

        failure_reason = ''
        with WorldSaver():
            # set to start conf
            if start_configuration is not None:
                self.client.set_robot_configuration(robot, start_configuration)

            if planner_id == 'IterativeIK':
                selected_links = [
                    link_from_name(robot_uid, l)
                    for l in robot.get_link_names(group=group)
                ]
                # with HideOutput():
                # with redirect_stdout():
                path = plan_cartesian_motion_from_links(
                    robot_uid,
                    selected_links,
                    tool_link,
                    ee_poses,
                    get_sub_conf=False,
                    pos_tolerance=pos_tolerance,
                    ori_tolerance=ori_tolerance)
                if path is None:
                    failure_reason = 'IK plan is not found.'
                # collision checking is not included in the default Cartesian planning
                if path is not None and avoid_collisions:
                    for i, conf_val in enumerate(path):
                        pruned_conf_val = self._prune_configuration(
                            robot_uid, conf_val, joint_names)
                        for attachment in attachments:
                            attachment.assign()
                        if collision_fn(pruned_conf_val, diagnosis=diagnosis):
                            failure_reason = 'IK plan is found but collision violated.'
                            path = None
                            break
                        path[i] = pruned_conf_val

                # TODO check joint threshold
            elif planner_id == 'LadderGraph':
                # get ik fn from client
                # collision checking is turned off because collision checking is handled inside LadderGraph planner
                ik_options = {'avoid_collisions': False, 'return_all': True}
                sample_ik_fn = ik_function or self._get_sample_ik_fn(
                    robot, ik_options)

                # convert ee_variant_fn
                if frame_variant_gen is not None:

                    def sample_ee_fn(pose):
                        for v_frame in frame_variant_gen.generate_frame_variant(
                                frame_from_pose(pose)):
                            yield pose_from_frame(v_frame)
                else:
                    sample_ee_fn = None

                path, cost = plan_cartesian_motion_lg(robot_uid, ik_joints, ee_poses, sample_ik_fn, collision_fn, \
                    jump_threshold=jump_threshold_from_joint, sample_ee_fn=sample_ee_fn)

                if verbose:
                    print('Ladder graph cost: {}'.format(cost))
            else:
                raise ValueError('Cartesian planner {} not implemented!',
                                 planner_id)

        if path is None:
            if verbose:
                cprint(
                    'No Cartesian motion found, due to {}!'.format(
                        failure_reason), 'red')
            return None
        else:
            # TODO start_conf might have different number of joints with the given group?
            start_traj_pt = None
            if start_configuration is not None:
                start_traj_pt = JointTrajectoryPoint(
                    values=start_configuration.values,
                    types=start_configuration.types)
                start_traj_pt.joint_names = start_configuration.joint_names

            jt_traj_pts = []
            for i, conf in enumerate(path):
                jt_traj_pt = JointTrajectoryPoint(values=conf,
                                                  types=joint_types)
                jt_traj_pt.joint_names = joint_names
                if start_traj_pt is not None:
                    # ! TrajectoryPoint doesn't copy over joint_names...
                    jtp = start_traj_pt.copy()
                    jtp.joint_names = start_traj_pt.joint_names
                    jtp.merge(jt_traj_pt)
                    jt_traj_pt = jtp
                jt_traj_pt.time_from_start = Duration(i * 1, 0)
                jt_traj_pts.append(jt_traj_pt)

            if start_configuration is not None and not compare_configurations(
                    start_configuration,
                    jt_traj_pts[0],
                    jump_threshold,
                    verbose=verbose):
                # if verbose:
                #     print()
                #     cprint('Joint jump from start conf, max diff {}'.format(start_configuration.max_difference(jt_traj_pts[0])), 'red')
                #     cprint('start conf {}'.format(['{:.4f}'.format(v) for v in start_configuration.values]), 'red')
                #     cprint('traj pt 0  {}'.format(['{:.4f}'.format(v) for v in jt_traj_pts[0].values]), 'red')
                pass
                # return None
            # TODO check intermediate joint jump
            trajectory = JointTrajectory(
                trajectory_points=jt_traj_pts,
                joint_names=jt_traj_pts[0].joint_names,
                start_configuration=jt_traj_pts[0],
                fraction=1.0)
            return trajectory
Esempio n. 5
0
        def inverse_kinematics(robot, frame_WCF, start_configuration=None, group=None, options=None):
            # TODO use input here instead of class attributes
                            #    avoid_collisions=True, constraints=None,
                            #    attempts=8, attached_collision_meshes=None,
                            #    return_full_configuration=False,
                            #    cull=False,
                            #    return_closest_to_start=False,
                            #    return_idxs=None):
            avoid_collisions = is_valid_option(options, 'avoid_collisions', True)
            constraints = is_valid_option(options, 'constraints', None)
            attempts = is_valid_option(options, 'attempts', 8)
            attached_collision_meshes = is_valid_option(options, 'attached_collision_meshes', None)
            return_full_configuration = is_valid_option(options, 'return_full_configuration', False)
            cull = is_valid_option(options, 'cull', False)
            return_closest_to_start = is_valid_option(options, 'return_closest_to_start', False)
            return_idxs = is_valid_option(options, 'return_idxs', None)

            if start_configuration:
                base_frame = robot.get_base_frame(self.group, full_configuration=start_configuration)
                self.update_base_transformation(base_frame)
                # in_collision = robot.client.configuration_in_collision(start_configuration)
                # if in_collision:
                #    raise ValueError("Start configuration already in collision")

            # frame_tool0_RCF = self.convert_frame_wcf_to_frame_tool0_rcf(frame_WCF)
            frame_tool0_RCF = Frame.from_transformation(self.base_transformation * Transformation.from_frame(frame_WCF) * self.tool_transformation)

            # call the ik function
            configurations = self.function(frame_tool0_RCF)

            # The ik solution for 6 axes industrial robots returns by default 8
            # configurations, which are sorted. That means, the if you call ik
            # on 2 frames that are close to each other, and compare the 8
            # configurations of the first one with the 8 of the second one at
            # their respective indices, then these configurations are 'close' to
            # each other. That is why for certain use cases, e.g. custom cartesian
            # path planning it makes sense to keep the sorting and set the ones
            # that are out of joint limits or in collison to `None`.

            if return_idxs:
                configurations = [configurations[i] for i in return_idxs]

            # add joint names to configurations
            self.add_joint_names_to_configurations(configurations)

            # fit configurations within joint bounds (sets those to `None` that are not working)
            self.try_to_fit_configurations_between_bounds(configurations)
            # check collisions for all configurations (sets those to `None` that are not working)
            # TODO defer collision checking
            # if robot.client:
            #     robot.client.check_configurations_for_collision(configurations)

            if return_closest_to_start:
                diffs = [c.max_difference(start_configuration) for c in configurations if c is not None]
                if len(diffs):
                    idx = diffs.index(min(diffs))
                    return configurations[idx]  # only one
                return None

            if cull:
                configurations = [c for c in configurations if c is not None]

            return configurations