Skip to content

Commit 17f9c8a

Browse files
authored
[BUG FIX] Fix support of unspecified origin in URDF. (#2499)
1 parent 0c2a68f commit 17f9c8a

File tree

4 files changed

+54
-43
lines changed

4 files changed

+54
-43
lines changed

genesis/engine/entities/rigid_entity/rigid_link.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -659,11 +659,18 @@ def _build(self):
659659
)
660660
if self._inertial_mass is None:
661661
self._inertial_mass = hint_mass
662-
if self._inertial_pos is None:
662+
if self._inertial_pos is None or self._inertial_i is None:
663+
if self._inertial_pos is not None and self._inertial_i is None:
664+
gs.logger.warning(
665+
f"Ignoring center of mass of link '{self.name}' because inertia matrix is not specified."
666+
)
667+
elif self._inertial_pos is None and self._inertial_i is not None:
668+
gs.logger.warning(
669+
f"Ignoring inertia matrix of link '{self.name}' because center of mass is not specified."
670+
)
663671
self._inertial_pos = hint_com
664-
self._inertial_quat = gu.identity_quat()
665-
if self._inertial_i is None:
666672
self._inertial_i = hint_inertia
673+
self._inertial_quat = gu.identity_quat()
667674

668675
# FIXME: Setting zero mass even for fixed links breaks physics for some reason...
669676
# For non-fixed links, it must be non-zero in case for coupling with deformable body solvers.

genesis/ext/urdfpy/urdf.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,12 +1100,12 @@ def origin(self):
11001100

11011101
@origin.setter
11021102
def origin(self, value):
1103-
self._origin = configure_origin(value)
1103+
self._origin = configure_origin(value, default=True)
11041104

11051105
@classmethod
11061106
def _from_xml(cls, node, root, path):
11071107
kwargs = cls._parse(node, root, path)
1108-
kwargs["origin"] = parse_origin(node)
1108+
kwargs["origin"] = parse_origin(node, default=True)
11091109
return Collision(**kwargs)
11101110

11111111
def _to_xml(self, parent, path):
@@ -1196,7 +1196,7 @@ def origin(self):
11961196

11971197
@origin.setter
11981198
def origin(self, value):
1199-
self._origin = configure_origin(value)
1199+
self._origin = configure_origin(value, default=True)
12001200

12011201
@property
12021202
def material(self):
@@ -1213,7 +1213,7 @@ def material(self, value):
12131213
@classmethod
12141214
def _from_xml(cls, node, root, path):
12151215
kwargs = cls._parse(node, root, path)
1216-
kwargs["origin"] = parse_origin(node)
1216+
kwargs["origin"] = parse_origin(node, default=True)
12171217
return Visual(**kwargs)
12181218

12191219
def _to_xml(self, parent, path):
@@ -1296,11 +1296,11 @@ def origin(self):
12961296

12971297
@origin.setter
12981298
def origin(self, value):
1299-
self._origin = configure_origin(value)
1299+
self._origin = configure_origin(value, default=False)
13001300

13011301
@classmethod
13021302
def _from_xml(cls, node, root, path):
1303-
origin = parse_origin(node)
1303+
origin = parse_origin(node, default=False)
13041304
mass = float(node.find("mass").attrib["value"])
13051305
n = node.find("inertia")
13061306
xx = float(n.attrib["ixx"])
@@ -1314,7 +1314,8 @@ def _from_xml(cls, node, root, path):
13141314

13151315
def _to_xml(self, parent, path):
13161316
node = ET.Element("inertial")
1317-
node.append(unparse_origin(self.origin))
1317+
if self.origin is not None:
1318+
node.append(unparse_origin(self.origin))
13181319
mass = ET.Element("mass")
13191320
mass.attrib["value"] = str(self.mass)
13201321
node.append(mass)
@@ -2226,7 +2227,7 @@ def origin(self):
22262227

22272228
@origin.setter
22282229
def origin(self, value):
2229-
self._origin = configure_origin(value)
2230+
self._origin = configure_origin(value, default=True)
22302231

22312232
@property
22322233
def limit(self):
@@ -2436,7 +2437,7 @@ def _from_xml(cls, node, root, path):
24362437
if axis is not None:
24372438
axis = np.fromstring(axis.attrib["xyz"], sep=" ")
24382439
kwargs["axis"] = axis
2439-
kwargs["origin"] = parse_origin(node)
2440+
kwargs["origin"] = parse_origin(node, default=True)
24402441
return Joint(**kwargs)
24412442

24422443
def _to_xml(self, parent, path):

genesis/ext/urdfpy/utils.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def xyz_rpy_to_matrix(xyz_rpy):
132132
return matrix
133133

134134

135-
def parse_origin(node):
135+
def parse_origin(node, *, default):
136136
"""Find the ``origin`` subelement of an XML node and convert it
137137
into a 4x4 homogenous transformation matrix.
138138
@@ -149,8 +149,11 @@ def parse_origin(node):
149149
``origin`` child. Defaults to the identity matrix if no ``origin``
150150
child was found.
151151
"""
152-
matrix = np.eye(4, dtype=np.float64)
153152
origin_node = node.find("origin")
153+
if not default and origin_node is None:
154+
return
155+
156+
matrix = np.eye(4, dtype=np.float64)
154157
if origin_node is not None:
155158
if "xyz" in origin_node.attrib:
156159
matrix[:3, 3] = np.fromstring(origin_node.attrib["xyz"], sep=" ")
@@ -273,7 +276,7 @@ def load_meshes(filename):
273276
return meshes
274277

275278

276-
def configure_origin(value):
279+
def configure_origin(value, *, default):
277280
"""Convert a value into a 4x4 transform matrix.
278281
279282
Parameters
@@ -288,8 +291,8 @@ def configure_origin(value):
288291
The created matrix.
289292
"""
290293
if value is None:
291-
value = np.eye(4, dtype=np.float64)
292-
elif isinstance(value, (list, tuple, np.ndarray)):
294+
return np.eye(4, dtype=np.float64) if default else None
295+
if isinstance(value, (list, tuple, np.ndarray)):
293296
value = np.asanyarray(value, dtype=np.float64)
294297
if value.shape == (6,):
295298
value = xyz_rpy_to_matrix(value)

genesis/utils/urdf.py

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -127,17 +127,18 @@ def parse_urdf(morph, surface):
127127
# we compute urdf's invweight later
128128
l_info["invweight"] = np.full((2,), fill_value=-1.0)
129129

130-
if link.inertial is None:
131-
l_info["inertial_pos"] = gu.zero_pos()
132-
l_info["inertial_quat"] = gu.identity_quat()
133-
l_info["inertial_i"] = None
134-
l_info["inertial_mass"] = None
135-
136-
else:
137-
l_info["inertial_pos"] = link.inertial.origin[:3, 3]
138-
l_info["inertial_quat"] = gu.R_to_quat(link.inertial.origin[:3, :3])
139-
l_info["inertial_i"] = link.inertial.inertia
140-
l_info["inertial_mass"] = link.inertial.mass
130+
l_info["inertial_pos"] = None
131+
l_info["inertial_quat"] = gu.identity_quat()
132+
l_info["inertial_i"] = None
133+
l_info["inertial_mass"] = None
134+
if link.inertial is not None:
135+
if link.inertial.origin is not None:
136+
l_info["inertial_pos"] = link.inertial.origin[:3, 3]
137+
l_info["inertial_quat"] = gu.R_to_quat(link.inertial.origin[:3, :3])
138+
if link.inertial.inertia is not None:
139+
l_info["inertial_i"] = link.inertial.inertia
140+
if link.inertial.mass is not None:
141+
l_info["inertial_mass"] = link.inertial.mass
141142

142143
for geom_prop in (*link.collisions, *link.visuals):
143144
geometry = geom_prop.geometry.geometry
@@ -348,16 +349,14 @@ def parse_urdf(morph, surface):
348349
# Apply scaling factor
349350
for l_info, link_j_infos, link_g_infos in zip(l_infos, links_j_infos, links_g_infos):
350351
l_info["pos"] *= morph.scale
351-
l_info["inertial_pos"] *= morph.scale
352-
352+
if l_info["inertial_pos"] is not None:
353+
l_info["inertial_pos"] *= morph.scale
353354
if l_info["inertial_mass"] is not None:
354355
l_info["inertial_mass"] *= morph.scale**3
355356
if l_info["inertial_i"] is not None:
356357
l_info["inertial_i"] *= morph.scale**5
357-
358358
for j_info in link_j_infos:
359359
j_info["pos"] *= morph.scale
360-
361360
for g_info in link_g_infos:
362361
g_info["pos"] *= morph.scale
363362

@@ -478,22 +477,23 @@ def merge_inertia(link1, link2):
478477
"""Combine two links with fixed joint."""
479478
if link2.inertial is None:
480479
return
480+
elif link2.inertial.origin is None:
481+
link2.inertial.origin = np.eye(4, dtype=np.float64)
481482

482483
if link1.inertial is None:
483484
link1.inertial = link2.inertial
484485
return
486+
elif link1.inertial.origin is None:
487+
link1.inertial.origin = np.eye(4, dtype=np.float64)
485488

486489
m1 = link1.inertial.mass
487490
m2 = link2.inertial.mass
488491

489-
com1 = link1.inertial.origin[:3, 3]
490-
com2 = link2.inertial.origin[:3, 3]
491-
492-
R1 = link1.inertial.origin[:3, :3]
493-
R2 = link2.inertial.origin[:3, :3]
492+
com1, R1 = link1.inertial.origin[:3, 3], link1.inertial.origin[:3, :3]
493+
com2, R2 = link2.inertial.origin[:3, 3], link2.inertial.origin[:3, :3]
494494

495495
combined_mass = m1 + m2
496-
if combined_mass > 0:
496+
if combined_mass > gs.EPS:
497497
combined_com = (m1 * com1 + m2 * com2) / combined_mass
498498
else:
499499
combined_com = com1
@@ -533,12 +533,12 @@ def update_subtree(links, joints, root_name, transform):
533533

534534
# Apply the transformation to the current link
535535
if current_link.inertial is not None:
536-
current_link.inertial.origin = transform @ current_link.inertial.origin
537-
538-
for geom in current_link.visuals:
539-
geom.origin = transform @ geom.origin
536+
if current_link.inertial.origin is None:
537+
current_link.inertial.origin = transform
538+
else:
539+
current_link.inertial.origin = transform @ current_link.inertial.origin
540540

541-
for geom in current_link.collisions:
541+
for geom in (*current_link.visuals, *current_link.collisions):
542542
geom.origin = transform @ geom.origin
543543

544544
for joint in joints:

0 commit comments

Comments
 (0)