예제 #1
0
def test_captured_exception_in_thread():
    def async_fn(callback):
        def runner(cb):
            raise ValueError('exception')

        Thread(target=runner, args=(callback, )).start()

    with pytest.raises(ValueError):
        await_callback(async_fn)
예제 #2
0
def test_errback():
    def async_fn(callback, errback):
        def runner(cb, eb):
            eb(ValueError('exception via errback'))

        Thread(target=runner, args=(callback, errback)).start()

    with pytest.raises(ValueError):
        await_callback(async_fn, errback_name='errback')
예제 #3
0
    def plan_motion(self,
                    robot,
                    goal_constraints,
                    start_configuration,
                    group,
                    path_constraints=None,
                    trajectory_constraints=None,
                    planner_id='',
                    num_planning_attempts=8,
                    allowed_planning_time=2.,
                    max_velocity_scaling_factor=1.,
                    max_acceleration_scaling_factor=1.,
                    attached_collision_meshes=None,
                    workspace_parameters=None):
        kwargs = {}
        kwargs['robot'] = robot
        kwargs['goal_constraints'] = goal_constraints
        kwargs['start_configuration'] = start_configuration
        kwargs['group'] = group
        kwargs['path_constraints'] = path_constraints
        kwargs['trajectory_constraints'] = trajectory_constraints
        kwargs['planner_id'] = planner_id
        kwargs['num_planning_attempts'] = num_planning_attempts
        kwargs['allowed_planning_time'] = allowed_planning_time
        kwargs['max_velocity_scaling_factor'] = max_velocity_scaling_factor
        kwargs[
            'max_acceleration_scaling_factor'] = max_acceleration_scaling_factor
        kwargs['attached_collision_meshes'] = attached_collision_meshes
        kwargs['workspace_parameters'] = workspace_parameters

        kwargs['errback_name'] = 'errback'

        return await_callback(self.plan_motion_async, **kwargs)
예제 #4
0
    def load_urdf(self, parameter_name='/robot_description'):
        """Loads a URDF model from the specified ROS parameter.

        Parameters
        ----------
        parameter_name : str, optional
            Name of the ROS parameter containing the robot description.
            Defaults to ``/robot_description``.

        Returns
        -------
        str
            URDF model of the robot currently loaded in ROS.
        """
        if self.local_cache_enabled and self.robot_name:
            filename = self._urdf_filename

            if _cache_file_exists(filename):
                return _read_file(filename)

        param = roslibpy.Param(self.ros, parameter_name)
        urdf = await_callback(param.get)

        self.robot_name = self._read_robot_name(urdf)

        if self.local_cache_enabled:
            # Retrieve filename again, now that robot_name
            # has been loaded from URDF
            _write_file(self._urdf_filename, urdf)

        return urdf
예제 #5
0
    def load_srdf(self, parameter_name='/robot_description_semantic'):
        """Loads an SRDF model from the specified ROS parameter.

        Parameters
        ----------
        parameter_name : str, optional
            Name of the ROS parameter containing the robot semantics.
            Defaults to ``/robot_description_semantic``.

        Returns
        -------
        str
            SRDF model of the robot currently loaded in ROS.
        """
        if self.local_cache_enabled and self.robot_name:
            filename = self._srdf_filename

            if _cache_file_exists(filename):
                return _read_file(filename)

        param = roslibpy.Param(self.ros, parameter_name)
        srdf = await_callback(param.get)

        if self.local_cache_enabled:
            _write_file(self._srdf_filename, srdf)

        return srdf
    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 zero 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.
              Defaults to the model's root 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``.
            - ``"timeout"``: (:obj:`int`, optional) Maximum allowed time for inverse kinematic calculation in seconds.
              Defaults to ``2``. This value supersedes the ``"attempts"`` argument used before ROS Noetic.
            - ``"attached_collision_meshes"``: (:obj:`list` of :class:`compas_fab.robots.AttachedCollisionMesh`, optional)
              Defaults to ``None``.
            - ``"attempts"``: (:obj:`int`, optional) The maximum number of inverse kinematic attempts.
              Defaults to ``8``. This value is ignored on ROS Noetic and newer, use ``"timeout"`` instead.

        Raises
        ------
        compas_fab.backends.exceptions.BackendError
            If no configuration can be found.

        Returns
        -------
        :obj:`tuple` of :obj:`list`
            A tuple of 2 elements containing a list of joint positions and a list of matching joint names.
        """
        options = options or {}
        kwargs = {}
        kwargs['options'] = options
        kwargs['frame_WCF'] = frame_WCF
        kwargs['group'] = group
        kwargs['start_configuration'] = start_configuration
        kwargs['errback_name'] = 'errback'

        # Use base_link or fallback to model's root link
        options['base_link'] = options.get('base_link', robot.model.root.name)

        return await_callback(self.inverse_kinematics_async, **kwargs)
예제 #7
0
def test_async_fn_with_more_params():
    def async_fn(values, other_stuff, callback):
        def runner(cb):
            cb(200)

        Thread(target=runner, args=(callback, )).start()

    result = await_callback(async_fn, values=[1, 2, 3], other_stuff=None)
    assert result == 200
예제 #8
0
def test_void_return_callback():
    def async_fn(callback):
        def runner(cb):
            cb()

        Thread(target=runner, args=(callback, )).start()

    result = await_callback(async_fn)
    assert result is None
예제 #9
0
def test_single_positional_arg_callback():
    def async_fn(callback):
        def runner(cb):
            cb('only_return_value')

        Thread(target=runner, args=(callback, )).start()

    result = await_callback(async_fn)
    assert result == 'only_return_value'
예제 #10
0
def test_one_positional_arg_and_kwargs_callback():
    def async_fn(callback):
        def runner(cb):
            cb(1, retries=5)

        Thread(target=runner, args=(callback, )).start()

    result, kwargs = await_callback(async_fn)
    assert result == 1
    assert kwargs['retries'] == 5
예제 #11
0
def test_kwargs_callback():
    def async_fn(callback):
        def runner(cb):
            cb(name='Austin', last_name='Powers')

        Thread(target=runner, args=(callback, )).start()

    result = await_callback(async_fn)
    assert result['name'] == 'Austin'
    assert result['last_name'] == 'Powers'
예제 #12
0
    def forward_kinematics(self, robot, configuration, group, ee_link):
        kwargs = {}
        kwargs['robot'] = robot
        kwargs['configuration'] = configuration
        kwargs['group'] = group
        kwargs['ee_link'] = ee_link

        kwargs['errback_name'] = 'errback'

        return await_callback(self.forward_kinematics_async, **kwargs)
예제 #13
0
def test_many_positional_args_and_kwargs_callback():
    def async_fn(callback):
        def runner(cb):
            cb(4, 2, 3, retries=5)

        Thread(target=runner, args=(callback, )).start()

    a, b, c, kw = await_callback(async_fn)
    assert a == 4
    assert b == 2
    assert c == 3
    assert kw['retries'] == 5
예제 #14
0
def test_many_positional_args_callback():
    def async_fn(callback):
        def runner(cb):
            cb(1, 2)

        Thread(target=runner, args=(callback, )).start()

    result = await_callback(async_fn)
    assert result == (
        1,
        2,
    )
예제 #15
0
    def forward_kinematics(self, joint_positions, base_link, group,
                           joint_names, ee_link):
        kwargs = {}
        kwargs['joint_positions'] = joint_positions
        kwargs['base_link'] = base_link
        kwargs['group'] = group
        kwargs['joint_names'] = joint_names
        kwargs['ee_link'] = ee_link

        kwargs['errback_name'] = 'errback'

        return await_callback(self.forward_kinematics_async, **kwargs)
예제 #16
0
    def forward_kinematics(self,
                           robot,
                           configuration,
                           group=None,
                           options=None):
        """Calculate the robot's forward kinematic.

        Parameters
        ----------
        robot : :class:`compas_fab.robots.Robot`
            The robot instance for which inverse kinematics is being calculated.
        configuration : :class:`compas_fab.robots.Configuration`
            The full configuration to calculate the forward kinematic for. If no
            full configuration is passed, the zero-joint state for the other
            configurable joints is assumed.
        group : str, optional
            Unused parameter.
        options : dict, optional
            Dictionary containing the following key-value pairs:

            - ``"base_link"``: (:obj:`str`) Name of the base link.
              Defaults to the model's root link.
            - ``"link"``: (:obj:`str`, optional) The name of the link to
              calculate the forward kinematics for. Defaults to the group's end
              effector link.
              Backwards compatibility note: if there's no ``link`` option, the
              planner will try also ``ee_link`` as fallback before defaulting
              to the end effector's link.

        Returns
        -------
        :class:`Frame`
            The frame in the world's coordinate system (WCF).
        """
        options = options or {}
        kwargs = {}
        kwargs['configuration'] = configuration
        kwargs['options'] = options
        kwargs['errback_name'] = 'errback'

        # Use base_link or fallback to model's root link
        options['base_link'] = options.get('base_link', robot.model.root.name)

        # Use selected link or default to group's end effector
        options['link'] = options.get(
            'link',
            options.get('ee_link')) or robot.get_end_effector_link_name(group)
        if options['link'] not in robot.get_link_names(group):
            raise ValueError(
                'Link name {} does not exist in planning group'.format(
                    options['link']))

        return await_callback(self.forward_kinematics_async, **kwargs)
예제 #17
0
    def reset_planning_scene(self, options=None):
        """Resets the planning scene, removing all added collision meshes.

        Parameters
        ----------
        options : dict, optional
            Unused parameter.

        Returns
        -------
        ``None``
        """
        kwargs = {}
        kwargs['errback_name'] = 'errback'

        return await_callback(self.reset_planning_scene_async, **kwargs)
예제 #18
0
    def get_planning_scene(self, options=None):
        """Retrieve the planning scene.

        Parameters
        ----------
        options : dict, optional
            Unused parameter.

        Returns
        -------
        :class:`compas_fab.backends.ros.messages.moveit_msgs.PlanningScene`
        """
        kwargs = {}
        kwargs['errback_name'] = 'errback'

        return await_callback(self.get_planning_scene_async, **kwargs)
예제 #19
0
    def plan_cartesian_motion(self, robot, frames, start_configuration, group,
                              max_step, jump_threshold, avoid_collisions,
                              path_constraints, attached_collision_meshes):
        kwargs = {}
        kwargs['robot'] = robot
        kwargs['frames'] = frames
        kwargs['start_configuration'] = start_configuration
        kwargs['group'] = group
        kwargs['max_step'] = max_step
        kwargs['jump_threshold'] = jump_threshold
        kwargs['avoid_collisions'] = avoid_collisions
        kwargs['path_constraints'] = path_constraints
        kwargs['attached_collision_meshes'] = attached_collision_meshes

        kwargs['errback_name'] = 'errback'

        return await_callback(self.plan_cartesian_motion_async, **kwargs)
예제 #20
0
    def load_mesh(self, url):
        """Loads a mesh from local storage.

        Parameters
        ----------
        url : str
            Mesh location

        Returns
        -------
        :class:`Mesh`
            Instance of a mesh.
        """
        use_local_file = False
        file_extension = _get_file_format(url)

        if self.local_cache_enabled:
            local_filename = self._local_mesh_filename(url)
            use_local_file = _cache_file_exists(local_filename)
        else:
            _, local_filename = tempfile.mkstemp(suffix='.' + file_extension, prefix='ros_fileserver_')

        if not use_local_file:
            service = roslibpy.Service(self.ros, '/file_server/get_file', 'file_server/GetBinaryFile')
            request = roslibpy.ServiceRequest(dict(name=url))
            response = await_callback(service.call, 'callback', 'errback', request)

            file_content = binascii.a2b_base64(response.data['value'])

            # Just look away, we're about to do something nasty!
            # namespaces are handled differently between the CLI and CPython
            # XML parsers, so, we just get rid of it for DAE files
            if file_extension == 'dae':
                file_content = file_content.replace(b'xmlns="http://www.collada.org/2005/11/COLLADASchema"', b'')

            # compas.files does not support file-like objects so we need to
            # save the file to disk always. If local caching is enabled,
            # we store it in the cache folder, otherwise, as a temp file.
            _write_file(local_filename, file_content, 'wb')
        else:
            # Nothing to do here, the file will be read by the mesh importer
            LOGGER.debug('Loading mesh file %s from local cache dir', local_filename)

        return _fileserver_mesh_import(url, local_filename)
    def add_attached_collision_mesh(self, attached_collision_mesh, options=None):
        """Add a collision mesh and attach it to the robot.

        Parameters
        ----------
        attached_collision_mesh : :class:`compas_fab.robots.AttachedCollisionMesh`
            Object containing the collision mesh to be attached.
        options : dict, optional
            Unused parameter.

        Returns
        -------
        ``None``
        """
        kwargs = {}
        kwargs['attached_collision_mesh'] = attached_collision_mesh
        kwargs['errback_name'] = 'errback'

        return await_callback(self.add_attached_collision_mesh_async, **kwargs)
    def remove_collision_mesh(self, id, options=None):
        """Remove a collision mesh from the planning scene.

        Parameters
        ----------
        id : str
            Name of collision mesh to be removed.
        options : dict, optional
            Unused parameter.

        Returns
        -------
        ``None``
        """
        kwargs = {}
        kwargs['id'] = id
        kwargs['errback_name'] = 'errback'

        return await_callback(self.remove_collision_mesh_async, **kwargs)
예제 #23
0
    def add_collision_mesh(self, collision_mesh, options=None):
        """Add a collision mesh to the planning scene.

        Parameters
        ----------
        collision_mesh : :class:`compas_fab.robots.CollisionMesh`
            Object containing the collision mesh to be added.
        options : dict, optional
            Unused parameter.

        Returns
        -------
        ``None``
        """
        kwargs = {}
        kwargs['collision_mesh'] = collision_mesh
        kwargs['errback_name'] = 'errback'

        return await_callback(self.add_collision_mesh_async, **kwargs)
예제 #24
0
    def compute_cartesian_path(self, frames, base_link, ee_link, group,
                               joint_names, joint_positions, max_step,
                               avoid_collisions, path_constraints,
                               attached_collision_object):
        kwargs = {}
        kwargs['frames'] = frames
        kwargs['base_link'] = base_link
        kwargs['ee_link'] = ee_link
        kwargs['group'] = group
        kwargs['joint_names'] = joint_names
        kwargs['joint_positions'] = joint_positions
        kwargs['max_step'] = max_step
        kwargs['avoid_collisions'] = avoid_collisions
        kwargs['path_constraints'] = path_constraints
        kwargs['attached_collision_object'] = attached_collision_object

        kwargs['errback_name'] = 'errback'

        return await_callback(self.compute_cartesian_path_async, **kwargs)
예제 #25
0
    def inverse_kinematics(self,
                           robot,
                           frame,
                           group,
                           start_configuration,
                           avoid_collisions=True,
                           constraints=None,
                           attempts=8,
                           attached_collision_meshes=None):
        kwargs = {}
        kwargs['robot'] = robot
        kwargs['frame'] = frame
        kwargs['group'] = group
        kwargs['start_configuration'] = start_configuration
        kwargs['avoid_collisions'] = avoid_collisions
        kwargs['constraints'] = constraints
        kwargs['attempts'] = attempts

        kwargs['errback_name'] = 'errback'

        return await_callback(self.inverse_kinematics_async, **kwargs)
예제 #26
0
    def motion_plan_goal_frame(self,
                               frame,
                               base_link,
                               ee_link,
                               group,
                               joint_names,
                               joint_positions,
                               tolerance_position,
                               tolerance_angle,
                               path_constraints=None,
                               trajectory_constraints=None,
                               planner_id='',
                               num_planning_attempts=8,
                               allowed_planning_time=2.,
                               max_velocity_scaling_factor=1.,
                               max_acceleration_scaling_factor=1.,
                               attached_collision_object=None):
        kwargs = {}
        kwargs['frame'] = frame
        kwargs['base_link'] = base_link
        kwargs['ee_link'] = ee_link
        kwargs['group'] = group
        kwargs['joint_names'] = joint_names
        kwargs['joint_positions'] = joint_positions
        kwargs['tolerance_position'] = tolerance_position
        kwargs['tolerance_angle'] = tolerance_angle
        kwargs['path_constraints'] = path_constraints
        kwargs['trajectory_constraints'] = trajectory_constraints
        kwargs['planner_id'] = planner_id
        kwargs['num_planning_attempts'] = num_planning_attempts
        kwargs['allowed_planning_time'] = allowed_planning_time
        kwargs['max_velocity_scaling_factor'] = max_velocity_scaling_factor
        kwargs[
            'max_acceleration_scaling_factor'] = max_acceleration_scaling_factor
        kwargs['attached_collision_object'] = attached_collision_object

        kwargs['errback_name'] = 'errback'

        return await_callback(self.motion_plan_goal_frame_async, **kwargs)
예제 #27
0
    def inverse_kinematics(self,
                           frame,
                           base_link,
                           group,
                           joint_names,
                           joint_positions,
                           avoid_collisions=True,
                           constraints=None,
                           attempts=8):
        kwargs = {}
        kwargs['frame'] = frame
        kwargs['base_link'] = base_link
        kwargs['group'] = group
        kwargs['joint_names'] = joint_names
        kwargs['joint_positions'] = joint_positions
        kwargs['avoid_collisions'] = avoid_collisions
        kwargs['constraints'] = constraints
        kwargs['attempts'] = attempts

        kwargs['errback_name'] = 'errback'

        return await_callback(self.inverse_kinematics_async, **kwargs)
예제 #28
0
    def get_planning_scene(self):
        kwargs = {}
        kwargs['errback_name'] = 'errback'

        return await_callback(self.get_planning_scene_async, **kwargs)
예제 #29
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 plan 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. Defaults to
            the all-zero configuration.
        group: str, optional
            The name of the group to plan for. 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.
            - ``"path_constraints"``: (:obj:`list` of :class:`compas_fab.robots.Constraint`, optional)
              Optional constraints that can be imposed along the solution path.
              Note that path calculation won't work if the start_configuration
              violates these constraints. Defaults to ``None``.
            - ``"planner_id"``: (:obj:`str`)
              The name of the algorithm used for path planning.
              Defaults to ``'RRTConnect'``.
            - ``"num_planning_attempts"``: (:obj:`int`, optional)
              Normally, if one motion plan is needed, one motion plan is computed.
              However, for algorithms that use randomization in their execution
              (like 'RRT'), it is likely that different planner executions will
              produce different solutions. Setting this parameter to a value above
              ``1`` will run many additional motion plans, and will report the
              shortest solution as the final result. Defaults to ``1``.
            - ``'allowed_planning_time'``: (:obj:`float`)
              The number of seconds allowed to perform the planning. Defaults to ``2``.
            - ``"max_velocity_scaling_factor"``: (:obj:`float`)
              Defaults to ``1``.
            - ``"max_acceleration_scaling_factor"``: (:obj:`float`)
              Defaults to ``1``.
            - ``"attached_collision_meshes"``: (:obj:`list` of :class:`compas_fab.robots.AttachedCollisionMesh`)
              Defaults to ``None``.

        Returns
        -------
        :class:`compas_fab.robots.JointTrajectory`
            The calculated trajectory.
        """
        options = options or {}
        kwargs = {}
        kwargs['goal_constraints'] = goal_constraints
        kwargs['start_configuration'] = start_configuration
        kwargs['group'] = group
        kwargs['options'] = options
        kwargs['errback_name'] = 'errback'

        # Use base_link or fallback to model's root link
        options['base_link'] = options.get('base_link', robot.model.root.name)
        options['joints'] = {j.name: j.type for j in robot.model.joints}

        return await_callback(self.plan_motion_async, **kwargs)
    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 cartesian motion plan 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. Defaults to
            the all-zero 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.
            - ``"link"``: (:obj:`str`, optional) The name of the link to
              calculate the forward kinematics for. Defaults to the group's end
              effector link.
            - ``"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`.
            - ``"avoid_collisions"``: (:obj:`bool`, optional)
              Whether or not to avoid collisions. Defaults to ``True``.
            - ``"path_constraints"``: (:obj:`list` of :class:`compas_fab.robots.Constraint`, optional)
              Optional constraints that can be imposed along the solution path.
              Note that path calculation won't work if the start_configuration
              violates these constraints. Defaults to ``None``.
            - ``"attached_collision_meshes"``: (:obj:`list` of :class:`compas_fab.robots.AttachedCollisionMesh`)
              Defaults to ``None``.

        Returns
        -------
        :class:`compas_fab.robots.JointTrajectory`
            The calculated trajectory.
        """
        options = options or {}
        kwargs = {}
        kwargs['options'] = options
        kwargs['frames_WCF'] = frames_WCF
        kwargs['start_configuration'] = start_configuration
        kwargs['group'] = group

        kwargs['errback_name'] = 'errback'

        # Use base_link or fallback to model's root link
        options['base_link'] = options.get('base_link', robot.model.root.name)
        options['joint_names'] = robot.get_configurable_joint_names()
        options['joint_types'] = robot.get_configurable_joint_types()
        options['link'] = options.get('link') or robot.get_end_effector_link_name(group)
        if options['link'] not in robot.get_link_names(group):
            raise ValueError('Link name {} does not exist in planning group'.format(options['link']))

        return await_callback(self.plan_cartesian_motion_async, **kwargs)