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")