Source code for pyrobopath.collision_detection.fcl_collision_models
from __future__ import annotations
import numpy as np
import quaternion
import fcl
import copy
from pyrobopath.tools.types import ArrayLike3, R3
from pyrobopath.tools.linalg import unit_vector2
from pyrobopath.toolpath.path.transform import Transform
from .collision_model import CollisionModel
[docs]
class FCLCollisionModel(CollisionModel):
"""A collision model using the `python-fcl <pythonfcl>`_ library
The in_collision function checks model collisions against other
FCLCollisionModel objects.
.. _pythonfcl: https://pypi.org/project/python-fcl/
"""
def __init__(self):
super(FCLCollisionModel, self).__init__()
self.obj: fcl.CollisionObject = None
self._collision_req = fcl.CollisionRequest(enable_contact=True)
def in_collision(self, other: CollisionModel) -> bool:
if not isinstance(other, FCLCollisionModel):
raise NotImplementedError
q1 = quaternion.as_float_array(self._transform.quat)
q2 = quaternion.as_float_array(other._transform.quat)
this_tf = fcl.Transform(q1, self._transform.t)
other_tf = fcl.Transform(q2, other._transform.t)
self.obj.setTransform(this_tf)
other.obj.setTransform(other_tf)
res = fcl.CollisionResult()
fcl.collide(self.obj, other.obj, self._collision_req, res)
return res.is_collision
def __getstate__(self):
state = self.__dict__.copy()
# remove unpickleable objects
state["obj"] = None
state["_collision_req"] = None
return state
def __setstate__(self, state):
self.__dict__ = state
self._collision_req = fcl.CollisionRequest(enable_contact=True)
[docs]
class FCLBoxCollisionModel(FCLCollisionModel):
"""An fcl box collision model.
:param x: length of the box
:type x: float
:param y: width of the box
:type y: float
:param z: height of the box
:type z: float
"""
def __init__(self, x: float, y: float, z: float):
super(FCLBoxCollisionModel, self).__init__()
self.dims = (x, y, z)
self.obj = fcl.CollisionObject(fcl.Box(*self.dims), fcl.Transform())
def __setstate__(self, state):
super().__setstate__(state)
self.obj = fcl.CollisionObject(fcl.Box(*self.dims), fcl.Transform())
[docs]
class FCLRobotBBCollisionModel(FCLBoxCollisionModel):
"""An FCL collision model for a robot's bounding box with rotation
about a planar anchor point and customizable center offset.
This model approximates a robot using a box aligned along the XY plane
that rotates around a vertical axis passing through `anchor`. The box
is positioned relative to an end-effector translation, and offset by a
specified vector to better fit the robot’s actual bounding shape.
See the pyrobopath documentation for further details.
:param dims: The dimensions of the box [length (X), width (Y), height (Z)].
:type dims: ArrayLike3
:param anchor: The 3D anchor point about which the box rotates. Typically
the robot base in world coordinates.
:type anchor: ArrayLike3
:param offset: An offset from the end-effector (tip) position to the center
of the box in the robot's frame. Allows fine-tuning of the
bounding volume shape.
:type offset: ArrayLike3
"""
def __init__(
self,
dims: ArrayLike3,
anchor: ArrayLike3 = np.zeros(3),
offset: ArrayLike3 = np.zeros(3),
):
super().__init__(*dims)
self._anchor = np.array(anchor)
self._eef_transform = Transform()
self._offset = np.array(offset)
self._box_center_in_eef = offset + np.array([-dims[0] * 0.5, 0.0, 0.0])
@property
def translation(self) -> R3:
return self._eef_transform.t
@translation.setter
def translation(self, value: R3):
p_eef_anchor = value[:2] - self.anchor[:2]
dir = unit_vector2(p_eef_anchor)
self._eef_transform = Transform.Rz(np.arctan2(dir[1], dir[0]))
self._eef_transform.t = value
self._transform.quat = self._eef_transform.quat
self._transform.t = self._eef_transform * self._box_center_in_eef
@property
def anchor(self):
return self._anchor
@anchor.setter
def anchor(self, value: ArrayLike3):
self._anchor = value
@property
def offset(self):
return self._offset
def _continuous_collision_check(
model1: FCLCollisionModel,
trans1_final: np.ndarray,
model2: FCLCollisionModel,
trans2_final: np.ndarray,
) -> bool:
t1_initial = fcl.Transform(model1.rotation, model1.translation)
t2_initial = fcl.Transform(model2.rotation, model2.translation)
model1.translation = trans1_final
model2.translation = trans2_final
t1_final = fcl.Transform(model1.rotation, model1.translation)
t2_final = fcl.Transform(model2.rotation, model2.translation)
model1.obj.setTransform(t1_initial)
model2.obj.setTransform(t2_initial)
request = fcl.ContinuousCollisionRequest()
request.ccd_motion_type = fcl.CCDMotionType.CCDM_LINEAR
request.ccd_solver_type = fcl.CCDSolverType.CCDC_CONSERVATIVE_ADVANCEMENT
request.gjk_solver_type = fcl.GJKSolverType.GST_LIBCCD
result = fcl.ContinuousCollisionResult()
ret = fcl.continuousCollide(
model1.obj, t1_final, model2.obj, t2_final, request, result
)
return result.is_collide