"""Slide the desk bell onto its coaster, without grasping it. RoboTwin has "Click Bell"; this is deliberately not that. The bell here is a plain rigid mesh with no plunger joint, so a "press" cannot register anything -- a rigid body on a rigid table does not move under a downward push, and the check would be measuring nothing (it read 0.0 mm). What IS measurable is sliding it: the bell is dome-shaped and top-heavy, so it tips easily, and the task is to get it onto the coaster while leaving it standing. """ from ..registry import register_task from ..envs import YamTaskEnv from ..solvers import push from .. import conditions as C @register_task("bell_coaster") class BellCoasterTask(YamTaskEnv): title = "SLIDE THE BELL ONTO ITS COASTER" tags = ["push", "no-grasp", "contact-rich", "robotwin-asset"] instruction = {"default": "Push the bell onto the coaster."} # 0.075 made a 14 cm desk bell; this is a believable 8.5 cm one. Mass 0.40 kg because a # desk bell IS heavy metal, and at 0.12 kg the dome tipped instead of sliding. rw_objects = {"rw_bell": "050_bell/base0.usd:0.045:0.40"} # Pushing toward -y means standing on the bell's +y side, and that stance sat 0.43 m from # the right shoulder -- out of reach, so nothing moved. The arm can only push AWAY from its # own shoulder at (-0.2,-0.2), so the route runs +x/+y instead. bell_spawn = (-0.02, -0.02) spawn_jitter = (0.018, 0.015) coaster_xy = (0.10, 0.02) coaster_size = 0.12 def _load_scene(self): self.scene.build_marker({"name": "coaster", "xy": self.coaster_xy, "size": self.coaster_size, "color": (0.30, 0.26, 0.22), "xy_jitter": (0.015, 0.015)}) self._placed = [self.scene.place_object( {"name": "rw_bell", "xy": self.bell_spawn, "xy_jitter": self.spawn_jitter})] def solve(self): e = self.scene.object_size("rw_bell") print(f"[task] bell {e.round(3)} m", flush=True) # a dome tips if shoved high up, so push low and slowly # push LOW on the dome: at 45% of its height it tipped over and rolled 15 cm past return push.solve(self, obj="rw_bell", target="coaster", advance=0.005, steps=300, push_z_frac=0.10, stop_dist=self.coaster_size*0.5) # NOT YET PASSING, and the remaining cause is the object, not the parameters. Three changes # each helped and none fixed it: # push_z_frac 0.45 -> 0.18 -> 0.10 (contact at the skirt) overshoot 0.154 -> 0.079 m # stop the push early and let it coast in overshoot -> 0.055 m # mass 0.12 -> 0.40 kg (a desk bell is heavy metal) overshoot 0.059 m # Position is now inside tolerance, but "still standing" keeps failing: a DOME rotates under # any horizontal contact, so it rolls rather than slides. The honest options are to push it # with a flat tool (a swept plate rather than a fingertip), or to replace the bell with an # object that has a flat side. Do not simply relax object_level to make it pass. def evaluate(self): return self.check( C.labelled("bell on the coaster", C.object_moved_to("rw_bell", "coaster", tol=0.06)), C.labelled("bell still standing", C.object_level("rw_bell", 25.0)))