2 Copyright (c) 2020 INRIA 
    3 Inspired from Antonio El Khoury PhD: 
    4 https://tel.archives-ouvertes.fr/file/index/docid/833019/filename/thesis.pdf 
    5 Section 3.8.1 Computing minimum bounding capsules 
    8 from pathlib 
import Path
 
   12 import scipy.optimize 
as optimize
 
   16 a, b: the two extremities of the capsule segment 
   17 r: radius of the capsule 
   21 CONSTRAINT_INFLATION_RATIO = 5e-3
 
   25     return np.linalg.norm(b - a) * np.pi * r**2 + 4 / 3 * np.pi * r**3
 
   31     t = ap.dot(ab) / ab.dot(ab)
 
   33     p_witness = a[
None, :] + (b - a)[
None, :] * t[:, 
None]
 
   34     dist = np.linalg.norm(p - p_witness, axis=1).
max()
 
   39     mean = vertices.mean(axis=0)
 
   41     u, s, vh = np.linalg.svd(vertices, full_matrices=
True)
 
   43     pca_proj = vertices.dot(components.T)
 
   46     a0 = mean + components[0] * (pca_proj[:, 0].
min() - EPSILON)
 
   47     b0 = mean + components[0] * (pca_proj[:, 0].
max() + EPSILON)
 
   48     radius = np.linalg.norm(pca_proj[:, 1:], axis=1).
max()
 
   54     constraint_inflation = CONSTRAINT_INFLATION_RATIO * r0
 
   55     x0 = np.array(list(a0) + list(b0) + [r0])
 
   57     def constraint_cap(x):
 
   63     constraint = optimize.NonlinearConstraint(
 
   64         constraint_cap, lb=-np.inf, ub=-constraint_inflation
 
   66     res = optimize.minimize(capsule_vol, x0, constraints=constraint)
 
   67     res_constraint = constraint_cap(res.x)
 
   69         "The computed solution is invalid, " 
   70         "a vertex is at a distance {:.5f} of the capsule." 
   72     assert res_constraint <= 1e-4, err.format(res_constraint)
 
   73     a, b, r = res.x[:3], res.x[3:6], res.x[6]
 
   78     mesh_loader = hppfcl.MeshLoader()
 
   79     mesh = mesh_loader.load(filename, np.ones(3))
 
   80     vertices = np.array([lMg * mesh.vertices(i) 
for i 
in range(mesh.num_vertices)])
 
   81     assert vertices.shape == (mesh.num_vertices, 3)
 
   87     from lxml 
import etree
 
   89     tree = etree.parse(infile)
 
   92         if fn.startswith(
"package://"):
 
   93             relpath = fn[len(
"package://") :]
 
   96             for rospath 
in os.environ[
"ROS_PACKAGE_PATH"].split(
":"):
 
   97                 abspath = Path(rospath) / relpath
 
  100             raise ValueError(
"Could not find " + fn)
 
  103     def get_transform(origin):
 
  104         from pinocchio 
import SE3, rpy
 
  106         _xyz = [float(v) 
for v 
in origin.attrib.get(
"xyz", 
"0 0 0").split(
" ")]
 
  107         _rpy = [float(v) 
for v 
in origin.attrib.get(
"rpy", 
"0 0 0").split(
" ")]
 
  108         return SE3(rpy.rpyToMatrix(*_rpy), np.array(_xyz))
 
  110     def set_transform(origin, a, b):
 
  111         from pinocchio 
import Quaternion, rpy
 
  113         length = np.linalg.norm(b - a)
 
  115         R = Quaternion.FromTwoVectors(np.array([0, 0, 1]), z).matrix()
 
  116         origin.attrib[
"xyz"] = 
" ".join([str(v) 
for v 
in ((a + b) / 2)])
 
  117         origin.attrib[
"rpy"] = 
" ".join([str(v) 
for v 
in rpy.matrixToRpy(R)])
 
  119     from tqdm 
import tqdm
 
  122         tree.xpath(
"/robot/link/collision/geometry/mesh"), desc=
"Generating capsules" 
  124         geom = mesh.getparent()
 
  125         coll = geom.getparent()
 
  126         link = coll.getparent()
 
  127         if coll.find(
"origin") 
is None:
 
  128             o = etree.Element(
"origin")
 
  131         origin = coll.find(
"origin")
 
  132         lMg = get_transform(origin)
 
  134         meshfile = get_path(mesh.attrib[
"filename"])
 
  136         name = Path(meshfile).name
 
  139         length = np.linalg.norm(b - a)
 
  141         set_transform(origin, a, b)
 
  143         mesh.tag = 
"cylinder" 
  144         mesh.attrib.pop(
"filename")
 
  145         mesh.attrib[
"radius"] = str(radius)
 
  146         mesh.attrib[
"length"] = str(length)
 
  147         coll.attrib[
"name"] = name
 
  149         if link.find(
"collision_checking") 
is None:
 
  150             link.append(etree.Element(
"collision_checking"))
 
  151         collision_checking = link.find(
"collision_checking")
 
  152         collision_checking.append(etree.Element(
"capsule"))
 
  153         collision_checking[-1].attrib[
"name"] = name
 
  158 if __name__ == 
"__main__":
 
  170     pinocchio_model_dir = Path(__file__).parent.parent / 
"models" 
  173         + 
"models/example-robot-data/robots/ur_description/urdf/ur5_gripper.urdf" 
  175     parse_urdf(urdf_filename, 
"ur5_gripper_with_capsules.urdf")