compas_fab.backends.pybullet.backend_features
¤
Backend feature implementations for the PyBullet backend.
Classes¤
PyBulletCheckCollision
¤
Methods:¤
check_collision
¤
check_collision(state: RobotCellState, options: dict | None = None)
Checks whether the current robot cell state with the given configuration is in collision.
The collision check involves the following steps:
- Check for collisions between each robot link.
- Check for collisions between each robot link and each tool.
- Check for collisions between each robot link and each rigid body.
- Check for collisions between each attached rigid body and all other rigid bodies.
- Check for collisions between each tool and each rigid body.
In each of the above steps, the collision check is skipped if the collision is allowed by the semantics of the robot, tool, or rigid body. For details, see in-line comments in code.
The passed state is stored by reference on the client while this function updates the planner
state. Pass state.copy() if you intend to mutate it after this call.
A collision report is returned with the CollisionCheckError exception message.
Following the fail-fast principle, the exception is raised when the first collision is detected.
The rest of the collision pairs are therefore not checked.
Setting the full_report option to True will change this behavior, forcing the collision
check to continue even after a collision is detected. In this case, a detailed report is returned
with all failing collision pairs. This can be useful for debugging.
configuration : :class:compas_fab.robots.Configuration
Configuration to be checked for collisions. If None is given, the current
configuration will be checked. Defaults to None.
verbose : :obj:bool
When True, additional information is printed. Defaults to False.
full_report : :obj:bool
A collision report is returned with the CollisionCheckError exception message.
Typically, the exception is raised on the first collision detected and the rest of
the collision pairs are not checked.
If full_report is set to True, the exception will be raised after all
collision pairs are checked, and the report will contain all failing collision pairs.
Defaults to False.
Parameters:
-
state(RobotCellState) –The robot cell state describing the robot cell. The attribute
robot_configuration, must contain the full configuration of the robot corresponding to the planning group. -
options(dict | None, default:None) –Dictionary containing the following key-value pairs:
"verbose": (:obj:bool, optional) WhenTrue, additional information is printed. Note that this significantly reduces performance. Defaults toFalse."full_report": (:obj:bool, optional) WhenTrue, all collision pairs are checked even when one encountered a collision. Defaults toFalse.
Raises:
-
CollisionCheckError–If the robot is in collision. The exception message contains a collision report.
check_collision_for_attached_objects_in_planning_group
¤
check_collision_for_attached_objects_in_planning_group(
state: RobotCellState, group: str, planner_coordinate_frame: Frame, options=None
)
A highly specific function for checking whether the attached tool and workpiece(s) for a particular target is in collision with stationary objects. This function is typically called by planning functions as part of a input sanity check or for checking whether targets are valid before motion planning.
This function is not a complete collision check but is intended to return quickly for input validation purposes. Because this function is used before motion planning, the robot configuration is not available.
The main difference between this function and the typical check_collision function is that this function only checks for collision
between the attached objects (tools and rigid bodies) and the stationary objects (other rigid bodies and tools) in the robot cell.
Only the attached objects that are related to the group and target are checked.
The robot is not checked for collision.
Internally this function depends on
:meth:~compas_fab.backends.pybullet.backend_features.PyBulletSetRobotCellState.set_robot_cell_state() and
:meth:~compas_fab.backends.pybullet.backend_features.PyBulletSetRobotCellState.set_attached_tool_and_rigid_body_state()
for setting the object states to the backend.
It will create a temporary robot_cell_state where the objects with unknown states are hidden and call
:meth:~compas_fab.backends.pybullet.backend_features.PyBulletCheckCollision.check_collision()
with only the cc4 and cc5 steps enabled.
PyBulletForwardKinematics
¤
Mix-in function to calculate the robot's forward kinematic.
Methods:¤
forward_kinematics
¤
forward_kinematics(
robot_cell_state: RobotCellState,
target_mode: TargetMode,
group: str | None = None,
native_scale: float | None = None,
options: dict | None = None,
) -> Frame
Calculate the target frame of the robot (relative to WCF) from the provided RobotCellState.
The returned coordinate frame is dependent on the chosen target_mode:
"Target.ROBOT"will return the planner coordinate frame (PCF)."Target.TOOL"will return the tool coordinate frame (TCF) if a tool is attached."Target.WORKPIECE"will return the workpiece's object coordinate frame (OCF) if a workpiece is attached (via an attached tool).
Collision checking is not performed during the calculation. Consider using the
:meth:compas_fab.backends.PyBulletCheckCollision.check_collision method to check for collisions.
Parameters:
-
robot_cell_state(RobotCellState) –The robot cell state describing the robot cell. The attribute
robot_configuration, must contain the full configuration of the robot corresponding to the planning group. The Configuration object must includejoint_names. The robot cell state should also reflect the attachment of tools, if any. -
target_mode(TargetMode) –The target mode to select which frame to return.
-
group(str | None, default:None) –The planning group of the robot. Defaults to the robot's main planning group.
-
native_scale(float | None, default:None) –The scaling factor to apply to the resulting frame. It is defined as
user_object_value * native_scale = meter_object_value. For example, if the resulting frame is to be used in a millimeters environment,native_scaleshould be set to'0.001'. Defaults to None, which means no scaling is applied. -
options(dict | None, default:None) –Dictionary for passing planner specific options. Currently unused.
Returns:
-
class:`Frame`–The frame in the world's coordinate system (WCF).
Raises:
-
:class:`compas_fab.backends.TargetModeMismatchError`–If the selected TargetMode is not possible with the provided robot cell state.
forward_kinematics_to_link
¤
forward_kinematics_to_link(
robot_cell_state: RobotCellState,
link_name: str | None = None,
native_scale: float | None = None,
options: dict | None = None,
)
Calculate the Link Coordinate Frame (LCF) to return. frame of the specified robot link from the provided RobotCellState.
This function operates similar to :meth:compas_fab.backends.PyBulletForwardKinematics.forward_kinematics,
but allows the user to specify which link's
link relative to the world coordinate frame (WCF).
This can be convenient in scenarios where user objects (such as a camera) are attached to one of the robot's links and the user needs to know the position of the object relative to the world coordinate frame.
Parameters:
-
robot_cell_state(RobotCellState) –The robot cell state describing the robot cell.
-
link_name(str | None, default:None) –The name of the link to calculate the forward kinematics for. Defaults to the last link of the provided planning group.
-
native_scale(float | None, default:None) –The scaling factor to apply to the resulting frame. It is defined as
user_object_value * native_scale = meter_object_value. For example, if the resulting frame is to be used in a millimeters environment,native_scaleshould be set to'0.001'. Defaults to None, which means no scaling is applied. -
options(dict | None, default:None) –Dictionary for passing planner specific options. Currently unused.
PyBulletInverseKinematics
¤
Mix-in functions to calculate the robot's inverse kinematics for a given target.
Methods:¤
inverse_kinematics
¤
inverse_kinematics(
target: FrameTarget,
robot_cell_state: RobotCellState,
group: str | None = None,
options: dict | None = None,
)
Calculate the robot's inverse kinematic for a given target.
The actual implementation can be found in the :meth:iter_inverse_kinematics method.
Calling inverse_kinematics() will return the first solution found by the iterator,
subsequent calls will return the next solution from the iterator. Once
all solutions have been exhausted, the iterator will be re-initialized.
Pybullet's inverse kinematics solver accepts FrameTarget and PointAxisTarget as input. The planner is a gradient descent solver, the initial position of the robot (supplied in the robot_cell_state) affects the first search attempt. Subsequent attempts will start from a random configuration, so the results may vary.
For target-specific implementation details, see
:meth:_iter_inverse_kinematics_frame_target for
:class:compas_fab.robots.FrameTarget and
:meth:_iter_inverse_kinematics_point_axis_target for
:class:compas_fab.robots.PointAxisTarget.
Parameters:
-
target(FrameTarget) –The target to calculate the inverse kinematics for.
-
robot_cell_state(RobotCellState) –The starting state to calculate the inverse kinematics for. The robot's configuration in the scene is taken as the starting configuration.
-
group(str | None, default:None) –The planning group used for calculation. Defaults to the robot's main planning group.
-
options(dict | None, default:None) –Dictionary containing kwargs for arguments specific to the underlying function being called. See the target-specific function's documentation for details.
Raises:
-
:class:`compas_fab.backends.exceptions.InverseKinematicsError`–If no configuration can be found.
-
:class:`compas_fab.backends.TargetModeMismatchError`–If the selected TargetMode is not possible with the provided robot cell state.
Returns:
-
obj:`compas_robots.Configuration`–One of the possible IK configurations that reaches the target.
iter_inverse_kinematics
¤
iter_inverse_kinematics(
target: Target,
robot_cell_state: RobotCellState,
group: str | None = None,
options: dict | None = None,
)
Calculate the robot's inverse kinematic for a given target.
Parameters:
-
target(Target) –The Frame Target to calculate the inverse for.
-
robot_cell_state(RobotCellState) –The starting state to calculate the inverse kinematics for.
-
group(str | None, default:None) –The planning group used for determining the end effector and labeling the
start_configuration. Defaults to the robot's main planning group. -
options(dict | None, default:None) –Dictionary containing the following key-value pairs:
Raises:
-
:class: `compas_fab.backends.exceptions.InverseKinematicsError`–If no configuration can be found.
-
:class:`compas_fab.backends.TargetModeMismatchError`–If the selected TargetMode is not possible with the provided robot cell state.
Yields:
-
obj:`compas_robots.Configuration`–One of the possible IK configurations that reaches the target.
PyBulletPlanCartesianMotion
¤
Callable to calculate a cartesian motion path (linear in tool space).
Methods:¤
plan_cartesian_motion
¤
plan_cartesian_motion(
waypoints: Waypoints,
start_state: RobotCellState,
group: str | None = None,
options: dict | None = None,
) -> JointTrajectory
Calculates a cartesian motion path (linear in tool space) for Waypoints.
Supports FrameWaypoints and PointAxisWaypoints.
For more information such as target specific behaviors, see
:meth:~compas_fab.backends.PyBulletClient.plan_cartesian_motion_point_axis_waypoints for
:class:~compas_fab.robots.PointAxisWaypoints and
:meth:~compas_fab.backends.PyBulletClient.plan_cartesian_motion_frame_waypoints for
:class:~compas_fab.robots.FrameWaypoints.
Note that the starting state of the robot cell must match with the objects in the robot cell
previously set using :meth:compas_fab.backends.PyBulletClient.set_robot_cell.
When a tool is attached to the planning group (specified in the start_state), it is possible
to use Waypoints.target_mode = TargetMode.TOOL.
When a workpiece is attached to the said tool, it is possible to use
Waypoints.target_mode = TargetMode.WORKPIECE.
The robot's full configuration, i.e. values for all configurable joints of the entire robot,
must be provided in the start_state.robot_configuration parameter.
The waypoints parameter is used to defined single or multiple segments of path,
it is not necessary to include the starting pose in the waypoints,
doing so may result in a redundant trajectory point.
Each path segment is interpolated linearly in Cartesian space, where position is interpolated linearly in Cartesian space and orientation is interpolated spherically using quaternion interpolation.
If the planning is successful, the function will return a :class:compas_fab.robots.JointTrajectory object,
which contains the start_configuration (same as input) and a list of :class:compas_fab.robots.JointTrajectoryPoint.
- Note that the first point in the trajectory will not repeat the start_configuration.
- Note that there may be one or more trajectory point for each path segment due to interpolation.
- Note that the returned trajectory does not separate the trajectory points into segments based on the waypoints. If the user wants to separate the path into segments, they should consider calling this function multiple times.
The check_collision parameter can be used to enable or disable collision checking during the planning process.
If enabled, the planner will check for collision at each trajectory point
similar to the :meth:compas_fab.backends.PyBulletClient.check_collision method.
Parameters:
-
waypoints(Waypoints) –The waypoints for the robot to follow.
-
start_state(RobotCellState) –The starting state of the robot cell at the beginning of the motion. The attribute
robot_configuration, must be provided. -
group(str | None, default:None) –The planning group used for calculation. Defaults to the robot's main planning group.
-
options(dict | None, default:None) –Dictionary containing additional options, see specific planner for details.
"check_collision": (:obj:str, optional) WhenTrue, :meth:compas_fab.backends.PyBulletCheckCollision.check_collisionwill be called. Defaults toTrue. Setting this to False may help during interactive debugging.
Returns:
-
class:`compas_fab.robots.JointTrajectory`–The calculated trajectory.
Raises:
-
:class:`compas_fab.backends.CollisionCheckError`–If
check_collisionis enabled and one of the configurations in the path is in collision. -
:class:`compas_fab.backends.InverseKinematicsError`–Indicates that one point along the path has no IK solution.
-
:class:`compas_fab.backends.TargetModeMismatchError`–If the selected TargetMode is not possible with the provided robot cell state.
Notes
This planning function is synchronous, meaning that it will block the main thread until the planning is complete. If the user wants to run the planning in the background and be able to cancel it, they should consider wrapping this function in a separate thread or process.
plan_cartesian_motion_frame_waypoints
¤
plan_cartesian_motion_frame_waypoints(
waypoints: FrameWaypoints,
start_state: RobotCellState,
group: str | None = None,
options: dict | None = None,
) -> JointTrajectory
Calculates a cartesian motion path (linear in tool space) for Frame Waypoints.
See :meth:~plan_cartesian_motion for the generic description of the planning function.
The following description is specific for Frame Waypoints.
The interpolation for Frame Waypoints is done in Cartesian space, meaning each segment (between two target_frames) creates a straight line in Cartesian space. The frame being interpolated is specified by the target_mode of the waypoints.
The planner will attempt to create equally spaced trajectory points between the waypoints.
The spacing is controlled by the max_step_distance and max_step_angle parameters.
These parameters are useful for controlling the smoothness of the trajectory.
However, even when two points are close in Cartesian space,
their joint positions may still be far apart.
This can be dangerous for the robot, as it may cause sudden high-speed jumps in
joint space, and large gaps in the collision checking process.
To prevent this, the planner will check whether the jump between each consecutive
joint position is within the maximum allowed distance specified by the
max_jump_prismatic and max_jump_revolute parameters.
If the jump is too large, the planner will subdivide the interpolation until the
jump is within the limits.
When this happens, the spacing between the interpolated points will no longer be equal.
The subdivision process have a maximum limit to prevent the planner from
subdividing indefinitely, such as the case near singularities.
This is controlled by the min_step_distance and min_step_angle parameters.
Typically, this is set to a small value 8 to 16 times smaller than the max_step parameters.
When the interpolation cannot be subdivided further, it is an indication that the robot
cannot reach the next point without making a large jump in joint space.
In this case, the planner will raise a JointJumpError and stop the planning process.
The following parameters are used to control the interpolation process. The interpolator will respect these parameters and will not exceed them, if the interpolation cannot be done within these limits, the planner will raise an error and stop the planning process.
Notes
Users should call :meth:~PyBulletPlanCartesianMotion.plan_cartesian_motion instead of this method directly.
Doing so will ensure that necessary scaling and checks are performed.
This planning function is deterministic, meaning that the same input will always result in the same output. If this function fails, retrying with the same input will not change the result. When planning fails, the user can still obtain the partial trajectory by
The planner will not attempt to avoid collision even when check_collision is enabled.
It simply check for them and raise an error if a collision is found.
This planning function is not asynchronous, meaning that it will block the main thread until the planning is complete or an error is raised.
Parameters:
-
waypoints(FrameWaypoints) –The waypoints for the robot to follow.
-
start_state(RobotCellState) –The starting state of the robot cell at the beginning of the motion. The attribute
robot_configuration, must be provided. -
group(str | None, default:None) –The planning group used for calculation.
-
options(dict | None, default:None) –Dictionary containing the following key-value pairs:
"max_step_distance": (:obj:float, optional) The max Cartesian distance between two consecutive points in the result. Unit is in meters, defaults to0.01."max_step_angle": (:obj:float, optional) The max angular distance between two consecutive points in the result. Unit is in radians, defaults to0.1."min_step_distance": (:obj:float, optional) The min Cartesian distance between two consecutive points when the interpolation is subdivided. Unit is in meters, defaults to0.0001."min_step_angle": (:obj:float, optional) The min angular distance between two consecutive points when the interpolation is subdivided. Unit is in radians, defaults to0.0001."max_jump_prismatic": (:obj:float, optional) The maximum allowed distance of prismatic joint positions between consecutive trajectory points. Unit is in meters, defaults to0.1. Setting this to0will disable this check."max_jump_revolute": (:obj:float, optional) The maximum allowed distance of revolute and continuous joint positions between consecutive trajectory points. Unit is in radians, defaults to :math:\pi / 2(90 degrees). Setting this to0will disable this check."check_collision": (:obj:bool, optional) Whether or not to avoid collision. Defaults toTrue."verbose": (:obj:bool, optional) Whether or not to print verbose output. Defaults toFalse."skip_preplanning_collision_check": (:obj:bool, optional) Whether or not to skip the target check before planning. Defaults toFalse. The preplanning check in intended to provide a fail-fast feedback for the user. However, if there are many targets in the waypoints, the check maybe more time consuming than the benefit it provides.
Returns:
-
class:`compas_fab.robots.JointTrajectory`–The calculated trajectory.
Raises:
-
:class:`compas_fab.backends.MPInterpolationInCollisionError`–If
check_collisionis enabled and the configuration is in collision. The partially planned trajectory before the collision is returned. The offending target and collision pairs are also returned. -
:class:`compas_fab.backends.MPNoIKSolutionError`–If no IK solution could be found by the kinematic solver. The partially planned trajectory before the unreachable target is returned. The unreachable target frame is returned.
-
:class:`compas_fab.backends.MPMaxJumpError`–If the joint positions between two consecutive points exceed the maximum allowed distance and the interpolation cannot be subdivided further due to the
min_step_*parameter. The joint name, type, values, and the difference are returned. -
:class:`compas_fab.backends.MPStartStateInCollisionError`–If the start state is in collision. This is part of a sanity check before the planning process.
plan_cartesian_motion_point_axis_waypoints
¤
plan_cartesian_motion_point_axis_waypoints(
waypoints: PointAxisWaypoints,
start_state: RobotCellState,
group: str | None = None,
options: dict | None = None,
) -> JointTrajectory
Calculates a cartesian motion path (linear in tool space) for Point Axis Waypoints.
Similar to the :meth:~plan_cartesian_motion_frame_waypoints method, this method calculates a cartesian motion path
(linear in tool space) for Point Axis Waypoints. The main difference is that the interpolation is
performed with the point and axis of the target. The main application of this function is
for 3D printing, milling, or other applications where the tool is cylindrical and must follow a specific path.
Users should select the correct target_mode in the waypoints to ensure that the planner interpolates correctly,
It is common to use TargetMode.TOOL for the example applications mentioned above, such that the reference frame
is the tool coordinate frame (TCF).
Note that the Z axis of the reference frame is aligned with the axis of the target.
Users who wish to use other axis should consider redefining the ToolModel.frame in their tools.
Users can choose to provide waypoints densely or sparsely. High density waypoints will allow the user to control precisely the path of the tool, such as when following a curve. This is because the planner is guaranteed to generate a JointTrajectoryPoint exactly at each waypoint. Sparse waypoints is suitable when the tool traces a straight line, the planner will create interpolated points between the waypoints automatically.
The interpolated points and axis have regular spacing between each waypoint segment.
The interpolation spacing is controlled by the max_step_distance and max_step_angle options,
they control the distance between points and the angle between axis. This setting affects the smoothness
of the trajectory.
Unlike the Cartesian motion planning with Frame Waypoints, the planner will not perform sub-division
of the interpolation when the joint jump is too large. It will simply reject the IK solution and try a different
tool orientation. Therefore, the user should not set the max_jump parameters too low, as it may prevent the
planner from finding a solution. For application that are not bounded by the joint speed, the user can set the
max_jump parameters to a high value (e.g. 0.1 meter and 1 pi radian), simply as a means to avoid sudden joint flips.
For applications that require a specific tool speed, the user can use the max_jump parameters as a rudimentary way of constraining the maximum difference between two consecutive points, hence keeping speed within the desired range.
The planning algorithm starts searching from the starting configuration and iteratively tries to find the next IK solution in the forward direction. When searching for the next IK solution, the planner will use the last configuration to perform a gradient decent, this is to ensure that the next configuration is close to the last one. However, this also limits the planner to not be able to jump to another configuration (e.g. from elbow up to elbow down). Users should therefore be aware that the planner is incomplete and may not find certain solutions even if one exists.
Because of the iterative search, the search space and searching time is highly sensitive to the starting configuration. A good starting configuration can greatly reduce the search time, while a bad starting configuration can make the search impossible. In general the user should provide a starting configuration that is likely to transition smoothly to all the waypoints.
The planner can be configured to sample the starting configuration to improve the chance of finding a solution,
this is controlled by the sample_start_configuration option. If enabled, the planner will treat the starting
configuration as an freely rotatable point-axis target. If a solution is found, the sampled configuration will be
returned in trajectory.start_configuration. In this mode, the start_state.robot_configuration is ignored.
Notes
Users should call :meth:~PyBulletPlanCartesianMotion.plan_cartesian_motion instead of this method directly.
Doing so will ensure that necessary scaling and checks are performed.
Parameters:
-
waypoints(PointAxisWaypoints) –The waypoints for the robot to follow.
-
start_state(RobotCellState) –The starting state of the robot cell at the beginning of the motion. The attribute
robot_configuration, must be provided. -
group(str | None, default:None) –The planning group used for calculation.
-
options(dict | None, default:None) –Dictionary containing the following key-value pairs:
"max_step_distance": (:obj:float, optional) The max Cartesian distance between two consecutive points in the result. Unit is in meters, defaults to0.01."max_step_angle": (:obj:float, optional) The max angular distance between two consecutive points in the result. Unit is in radians, defaults to0.1."sample_start_configuration": (:obj:bool, optional) Whether or not to sample the start configuration. Defaults toFalse."check_collision": (:obj:bool, optional) Whether or not to avoid collision. Defaults toTrue."max_jump_prismatic": (:obj:float, optional) The maximum allowed distance of prismatic joint positions between consecutive trajectory points. Unit is in meters, defaults to0.1. Setting this to0will disable this check."max_jump_revolute": (:obj:float, optional) The maximum allowed distance of revolute and continuous joint positions between consecutive trajectory points. Unit is in radians, defaults to :math:\pi / 2(90 degrees). Setting this to0will disable this check."check_collision": (:obj:bool, optional) Whether or not to avoid collision. Defaults toTrue."verbose": (:obj:bool, optional) Whether or not to print verbose output. Defaults toTrue."skip_preplanning_collision_check": (:obj:bool, optional) Whether or not to skip the target check before planning. Defaults toTrue. The preplanning check in intended to provide a fail-fast feedback for the user. However, if there are many targets in the waypoints, the check maybe more time consuming than the benefit it provides. Because this planner is often used for 3D printing, milling, etc. the user is likely to provide dense waypoints, therefore this check is disabled by default.
Returns:
-
class:`compas_fab.robots.JointTrajectory`–The calculated trajectory.
Raises:
-
:class:`compas_fab.backends.MPNoIKSolutionError`–If no IK solution could be found by the kinematic solver. The partially planned trajectory before the unreachable target is returned. The unreachable target frame is returned.
PyBulletSetRobotCell
¤
Methods:¤
set_robot_cell
¤
set_robot_cell(
robot_cell: RobotCell,
robot_cell_state: RobotCellState = None,
options: dict | None = None,
)
Pass the models in the robot cell to the Pybullet client.
The client keeps the robot cell models in memory and uses them for planning. Calling this method will override the previous robot cell in the client. It should be called only if the robot cell models have changed.
The RobotCell object passed to this method should not be modified after calling this method, as the client keeps a reference to it.
Parameters:
-
robot_cell(RobotCell) –The robot cell object containing the robot, tools, and other objects.
-
robot_cell_state(RobotCellState, default:None) –The state of the robot cell, including the robot's configuration.
-
options(dict | None, default:None) –Dictionary containing additional options. Unused at the moment.
PyBulletSetRobotCellState
¤
Methods:¤
set_attached_tool_and_rigid_body_state
¤
set_attached_tool_and_rigid_body_state(
state: RobotCellState, group: str, planner_coordinate_frame: Frame
)
Change the state of the models in the robot cell that have already been set to the Pybullet client.
Similar to the :meth:set_robot_cell_state method, but affects only the tools and rigid bodies
that are attached to the specified group.
set_robot_cell_state
¤
set_robot_cell_state(robot_cell_state: RobotCellState)
Change the state of the models in the robot cell that have already been set to the Pybullet client.
This function is typically called by planning functions that accepts a start state as input. The user should not need to call this function directly.
The passed robot_cell_state is stored by reference on the client.
Pass robot_cell_state.copy() if you intend to mutate it after this call.
The robot cell state must match the robot cell set earlier by :meth:set_robot_cell.
If the robot_configuration is provided, the robot's configuration is updated and the position of the attached tools and rigid bodies are also updated accordingly. Otherwise, their positions are left unchanged.
For stationary (non-attached) tools and rigid bodies, their positions are updated according to the provided frames in the tool_state and rigid_body_state respectively.
Hidden tools and rigid bodies are not updated.
Notes
All the magic for transforming the attached objects happens here.