2121_ROBOT_ON_STAND_USD_CACHE_DIR = "robot_on_stand"
2222
2323_HEIGHT_ATOL = 1e-3
24+ _FOOTPRINT_ATOL = 1e-3
2425_ALIGN_ATOL = 5e-2
2526
2627
@@ -52,17 +53,17 @@ class StandPrimSpec:
5253 ref_prim_path : str
5354 payload_child_name : str
5455 footprint_translate_xyz : tuple [float , float , float ]
55- footprint_scale_xy : tuple [float , float ]
56+ stand_default_footprint_xy_m : tuple [float , float ]
5657 stand_default_height : float
5758
5859
59- @functools .cache
6060def compose_on_stand_usd (
6161 robot : RobotPrimSpec ,
6262 stand : StandPrimSpec ,
6363 * ,
6464 stand_height_m : float ,
6565 output_basename : str ,
66+ stand_footprint_xy_m : tuple [float , float ] | None = None ,
6667) -> str :
6768 """Build a robot+stand USD with stand under the robot base link.
6869
@@ -74,15 +75,39 @@ def compose_on_stand_usd(
7475 stand: Stand reference and footprint parameters.
7576 stand_height_m: Target absolute stand height after align.
7677 output_basename: Stable basename for the composed USD (embodiment-specific).
78+ stand_footprint_xy_m: Target footprint ``(x, y)`` dimensions in meters. ``None`` uses the default.
7779
7880 Returns:
7981 Local path to the composed on-stand USD.
8082 """
8183 assert stand_height_m > 0.0 , f"stand_height_m must be positive, got { stand_height_m } "
84+ if stand_footprint_xy_m is None :
85+ stand_footprint_xy_m = stand .stand_default_footprint_xy_m
86+ assert len (stand_footprint_xy_m ) == 2 , f"stand_footprint_xy_m must have 2 values, got { stand_footprint_xy_m !r} "
87+ assert all (
88+ value > 0.0 for value in stand_footprint_xy_m
89+ ), f"stand_footprint_xy_m must be positive, got { stand_footprint_xy_m } "
90+ return _compose_on_stand_usd_cached (
91+ robot ,
92+ stand ,
93+ stand_height_m ,
94+ output_basename ,
95+ stand_footprint_xy_m ,
96+ )
97+
8298
99+ @functools .cache
100+ def _compose_on_stand_usd_cached (
101+ robot : RobotPrimSpec ,
102+ stand : StandPrimSpec ,
103+ stand_height_m : float ,
104+ output_basename : str ,
105+ stand_footprint_xy_m : tuple [float , float ],
106+ ) -> str :
83107 cache_root = get_arena_asset_cache_dir ().parent / "usd" / _ROBOT_ON_STAND_USD_CACHE_DIR
84108 cache_root .mkdir (parents = True , exist_ok = True )
85- out_path = cache_root / f"{ output_basename } _{ stand_height_m :.3f} .usd"
109+ footprint_x_m , footprint_y_m = stand_footprint_xy_m
110+ out_path = cache_root / f"{ output_basename } _{ stand_height_m } _{ footprint_x_m } x{ footprint_y_m } .usd"
86111
87112 with tempfile .NamedTemporaryFile (suffix = ".usd" , dir = cache_root , delete = False ) as tmp_file :
88113 tmp_path = Path (tmp_file .name )
@@ -94,7 +119,7 @@ def compose_on_stand_usd(
94119 root .GetReferences ().AddReference (robot_resolved , robot .root_prim_path )
95120 stage .SetDefaultPrim (root )
96121
97- _mount_stand_normalized (stage , robot , stand , stand_height_m )
122+ _add_stand (stage , robot , stand , stand_height_m , stand_footprint_xy_m )
98123 assert stage .GetRootLayer ().Save (), f"failed to save composed on-stand USD to { tmp_path } "
99124 os .replace (tmp_path , out_path )
100125 except Exception :
@@ -104,19 +129,20 @@ def compose_on_stand_usd(
104129 return str (out_path )
105130
106131
107- def _mount_stand_normalized (
132+ def _add_stand (
108133 stage : Usd .Stage ,
109134 robot : RobotPrimSpec ,
110135 stand : StandPrimSpec ,
111136 stand_height_m : float ,
137+ stand_footprint_xy_m : tuple [float , float ],
112138) -> None :
113- """Parent a stand payload under the robot base link, scale, and align to the robot base .
139+ """Add a stand under the robot base link with the requested size and alignment .
114140
115141 Composed USD hierarchy::
116142
117143 /panda/panda_link0/stand_instanceable # outer mount
118144 translate: (footprint_xy, align_z)
119- scale: (footprint_xy , stand_height_m / native_height)
145+ scale: (stand_footprint_xy_m / native_footprint_xy_m , stand_height_m / native_height)
120146 /<payload_child_name>/ # inner payload
121147 reference: stand USD @ ref_prim_path
122148
@@ -125,6 +151,7 @@ def _mount_stand_normalized(
125151 robot: Robot prim layout under the composed default prim.
126152 stand: Stand reference and footprint parameters.
127153 stand_height_m: Target stand height after align.
154+ stand_footprint_xy_m: Target footprint ``(x, y)`` dimensions in meters.
128155 """
129156 # Robot-only stage: bottom of the base link is the align target (before stand exists).
130157 robot_base = stage .GetPrimAtPath (robot .robot_base_prim_path )
@@ -136,36 +163,41 @@ def _mount_stand_normalized(
136163
137164 stand_resolved = retrieve_file_path (stand .stand_usd_path )
138165 tx , ty , tz = stand .footprint_translate_xyz
139- sx , sy = stand . footprint_scale_xy
166+ footprint_x_m , footprint_y_m = stand_footprint_xy_m
140167 stand_prim_path = robot .stand_prim_path
141168
142- # Outer mount under link0; Z scale stays at 1 until native height is measured.
169+ # Outer mount under link0 stays at unit scale until the source stand dimensions are measured.
143170 stand_xf = UsdGeom .Xform .Define (stage , stand_prim_path )
144171 translate_op = stand_xf .AddTranslateOp ()
145172 translate_op .Set (Gf .Vec3d (tx , ty , tz ))
146173 scale_op = stand_xf .AddScaleOp ()
147- scale_op .Set (Gf .Vec3d (sx , sy , 1.0 ))
174+ scale_op .Set (Gf .Vec3d (1.0 , 1.0 , 1.0 ))
148175
149176 payload_prim = stage .DefinePrim (f"{ stand_prim_path } /{ stand .payload_child_name } " )
150177 payload_prim .GetReferences ().AddReference (stand_resolved , stand .ref_prim_path )
151178
152179 stand_prim = stand_xf .GetPrim ()
153180 bbox_cache = UsdGeom .BBoxCache (Usd .TimeCode .Default (), [UsdGeom .Tokens .default_ ])
154181
155- # Native height at footprint XY scale; then scale Z to the requested stand height .
182+ # Scale the source stand to the requested physical dimensions .
156183 stand_range = bbox_cache .ComputeWorldBound (stand_prim ).ComputeAlignedRange ()
157184 assert not stand_range .IsEmpty (), f"empty stand bounds at { stand_prim .GetPath ()} "
158- native_height = float (stand_range .GetSize ()[2 ])
185+ native_size = stand_range .GetSize ()
186+ native_x_m , native_y_m , native_height = (float (value ) for value in native_size )
187+ assert native_x_m > 0.0 and native_y_m > 0.0 , f"non-positive stand footprint at { stand_prim .GetPath ()} "
159188 assert native_height > 0.0 , f"non-positive stand height at { stand_prim .GetPath ()} "
160- scale_op .Set (Gf .Vec3d (sx , sy , stand_height_m / native_height ))
189+ scale_op .Set (Gf .Vec3d (footprint_x_m / native_x_m , footprint_y_m / native_y_m , stand_height_m / native_height ))
161190
162191 # Raise/lower outer translate so stand top meets robot_min_z.
163192 translate_op .Set (Gf .Vec3d (tx , ty , robot_min_z ))
164193
165194 # Verify stand/robot alignment and stand height on a fresh BBoxCache.
166195 verify_cache = UsdGeom .BBoxCache (Usd .TimeCode .Default (), [UsdGeom .Tokens .default_ ])
167196 stand_range = verify_cache .ComputeWorldBound (stand_prim ).ComputeAlignedRange ()
168- stand_height = float (stand_range .GetSize ()[2 ])
197+ stand_size = stand_range .GetSize ()
198+ stand_x_m , stand_y_m , stand_height = (float (value ) for value in stand_size )
169199 stand_max_z = float (stand_range .GetMax ()[2 ])
200+ assert abs (stand_x_m - footprint_x_m ) < _FOOTPRINT_ATOL , stand_x_m
201+ assert abs (stand_y_m - footprint_y_m ) < _FOOTPRINT_ATOL , stand_y_m
170202 assert abs (stand_height - stand_height_m ) < _HEIGHT_ATOL , stand_height
171203 assert abs (stand_max_z - robot_min_z ) < _ALIGN_ATOL , (stand_max_z , robot_min_z )
0 commit comments