Skip to content

Commit 547d3fa

Browse files
authored
add back min-z detection (#67)
1 parent 9b68c04 commit 547d3fa

1 file changed

Lines changed: 71 additions & 0 deletions

File tree

urdf2mjcf/convert.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,65 @@ def mat_mult(mat_a: list[list[float]], mat_b: list[list[float]]) -> list[list[fl
122122
return result
123123

124124

125+
def compute_min_z(body: ET.Element, parent_transform: list[list[float]] | None = None) -> float:
126+
"""Recursively computes the minimum Z value in the world frame.
127+
128+
This is used to compute the starting height of the robot.
129+
130+
Args:
131+
body: The current body element.
132+
parent_transform: The transform of the parent body.
133+
134+
Returns:
135+
The minimum Z value in the world frame.
136+
"""
137+
if parent_transform is None:
138+
parent_transform = [
139+
[1.0, 0.0, 0.0, 0.0],
140+
[0.0, 1.0, 0.0, 0.0],
141+
[0.0, 0.0, 1.0, 0.0],
142+
[0.0, 0.0, 0.0, 1.0],
143+
]
144+
pos_str: str = body.attrib.get("pos", "0 0 0")
145+
quat_str: str = body.attrib.get("quat", "1 0 0 0")
146+
body_tf: list[list[float]] = mat_mult(parent_transform, build_transform(pos_str, quat_str))
147+
local_min_z: float = float("inf")
148+
149+
for child in body:
150+
if child.tag == "geom":
151+
gpos_str: str = child.attrib.get("pos", "0 0 0")
152+
gquat_str: str = child.attrib.get("quat", "1 0 0 0")
153+
geom_tf: list[list[float]] = build_transform(gpos_str, gquat_str)
154+
total_tf: list[list[float]] = mat_mult(body_tf, geom_tf)
155+
156+
# The translation part of T_total is in column 3.
157+
z: float = total_tf[2][3]
158+
geom_type: str = child.attrib.get("type", "")
159+
if geom_type == "box":
160+
size_vals: list[float] = list(map(float, child.attrib.get("size", "0 0 0").split()))
161+
half_height: float = size_vals[2] if len(size_vals) >= 3 else 0.0
162+
candidate: float = z - half_height
163+
elif geom_type == "cylinder":
164+
size_vals = list(map(float, child.attrib.get("size", "0 0").split()))
165+
half_length: float = size_vals[1] if len(size_vals) >= 2 else 0.0
166+
candidate = z - half_length
167+
elif geom_type == "sphere":
168+
r = float(child.attrib.get("size", "0"))
169+
candidate = z - r
170+
elif geom_type == "mesh":
171+
candidate = z
172+
else:
173+
candidate = z
174+
175+
local_min_z = min(candidate, local_min_z)
176+
177+
elif child.tag == "body":
178+
child_min: float = compute_min_z(child, body_tf)
179+
local_min_z = min(child_min, local_min_z)
180+
181+
return local_min_z
182+
183+
125184
def add_compiler(root: ET.Element) -> None:
126185
"""Add a compiler element to the MJCF root.
127186
@@ -786,6 +845,18 @@ def build_body(
786845
robot_body = build_body(root_link_name, None, actuator_joints)
787846
if robot_body is None:
788847
raise ValueError("Failed to build robot body")
848+
849+
# Gets the minimum z coordinate of the robot body.
850+
min_z: float = compute_min_z(robot_body)
851+
computed_offset: float = -min_z + metadata.height_offset
852+
logger.info("Auto-detected base offset: %s (min z = %s)", computed_offset, min_z)
853+
854+
# Moves the robot body to the computed offset.
855+
body_pos = robot_body.attrib.get("pos", "0 0 0")
856+
body_pos = [float(x) for x in body_pos.split()]
857+
body_pos[2] += computed_offset
858+
robot_body.attrib["pos"] = " ".join(f"{x:.8f}" for x in body_pos)
859+
789860
robot_body.attrib["childclass"] = ROBOT_CLASS
790861
worldbody.append(robot_body)
791862

0 commit comments

Comments
 (0)