UWLab / scripts_v2 /tools /compute_bottom_offset.py
Xinyi0214's picture
Upload UWLab (code, datasets, checkpoints, eval outputs)
0037d53 verified
Raw
History Blame Contribute Delete
2.77 kB
# Copyright (c) 2024-2026, The UW Lab Project Developers. (https://github.com/uw-lab/UWLab/blob/main/CONTRIBUTORS.md).
# All Rights Reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Compute the bottom offset of a USD asset.
The bottom offset is the distance from the asset's origin to the lowest
point of any mesh in the USD file. This value is used to spawn objects
flush on the table surface.
Usage:
python scripts_v2/tools/compute_bottom_offset.py /path/to/asset.usd
"""
import argparse
import numpy as np
from pxr import Usd, UsdGeom
def get_world_points(mesh: UsdGeom.Mesh, time=Usd.TimeCode.Default()) -> np.ndarray:
"""Get mesh vertices transformed to world space."""
points = np.array(mesh.GetPointsAttr().Get(time), dtype=np.float64)
if len(points) == 0:
return points
xform = UsdGeom.Xformable(mesh.GetPrim())
world_transform = xform.ComputeLocalToWorldTransform(time)
mat = np.array(world_transform, dtype=np.float64).T
ones = np.ones((len(points), 1), dtype=np.float64)
homogeneous = np.hstack([points, ones])
return (homogeneous @ mat.T)[:, :3]
def compute_bottom_offset(usd_path: str, verbose: bool = False) -> float:
"""Compute distance from origin to lowest mesh vertex in the USD."""
stage = Usd.Stage.Open(usd_path)
if not stage:
raise FileNotFoundError(f"Cannot open USD: {usd_path}")
all_points = []
mesh_count = 0
for prim in stage.Traverse():
if prim.IsA(UsdGeom.Mesh):
mesh = UsdGeom.Mesh(prim)
pts = get_world_points(mesh)
if len(pts) > 0:
all_points.append(pts)
mesh_count += 1
if not all_points:
raise ValueError(f"No meshes found in {usd_path}")
pts = np.vstack(all_points)
bbox_min = pts.min(axis=0)
bbox_max = pts.max(axis=0)
min_z = bbox_min[2]
if verbose:
print(f"Meshes found: {mesh_count}")
print(f"Total vertices: {len(pts)}")
print(f"Bounding box min: ({bbox_min[0]:.6f}, {bbox_min[1]:.6f}, {bbox_min[2]:.6f})")
print(f"Bounding box max: ({bbox_max[0]:.6f}, {bbox_max[1]:.6f}, {bbox_max[2]:.6f})")
size = bbox_max - bbox_min
print(f"Size: ({size[0]:.6f}, {size[1]:.6f}, {size[2]:.6f})")
return abs(min_z)
def main():
parser = argparse.ArgumentParser(description="Compute bottom offset of a USD asset.")
parser.add_argument("usd_path", type=str, help="Path to the USD file.")
parser.add_argument("-v", "--verbose", action="store_true", help="Print bounding box details.")
args = parser.parse_args()
offset = compute_bottom_offset(args.usd_path, verbose=args.verbose)
print(f"bottom_offset: {offset:.6f}")
if __name__ == "__main__":
main()