You are controlling a Franka Emika Panda robot that must assemble a piece of furniture in simulation (FurnitureBench / FurnitureSim). Goal: assemble the one part pair that this task scores. Call get_target_assembly() first -- it names the base part and the exact part you must attach to it. Only that pair counts: the scene contains four near-identical legs and seating the wrong one scores nothing. The leg is assembled once its bottom end is seated in a hole and aligned with the tabletop. Scene and conventions: - All positions are XYZ metres in the ROBOT BASE frame. All quaternions are WXYZ. - The table surface is at approximately z = 0. The parts start lying flat on it. - The gripper points straight down when its quaternion is (0, 1, 0, 0) WXYZ. - get_assembly_slots(base_part) returns the target POSES (position, quaternion_wxyz) the leg itself must reach. Drive the leg onto one of them; do not add your own offset for the leg's length, the pose already accounts for it. - sample_grasp_pose(part) already returns a graspable pose at the part's mid-height, so use it directly rather than the raw get_object_pose position, which sits at table level. - get_object_pose(name, return_bbox_extent=True) also returns the part's size, as full side lengths along the part's OWN axes. Use it instead of guessing dimensions. - Motions are blocking and the episode has a limited step budget, so avoid redundant moves. Key rules: - Always open the gripper before approaching a part, and use goto_pose(..., z_approach=...) so the gripper descends onto the part instead of colliding with it sideways. - After grasping, lift the leg clear of the table before moving it laterally. - The legs start lying flat, but a leg only seats in a hole when it is upright, so it has to be reoriented after it is picked up. - The legs are THREADED and screw into deep sockets. Pushing a held leg down does not work: the controller is compliant and the leg just catches on the rim. Instead, hold the leg upright just above the hole and RELEASE it, so it drops in and seats under its own weight. Only then regrasp it from above and screw it home: rotate it about its vertical axis while pressing gently down. There is no primitive for any of this; compose it from goto_pose and the gripper calls. - The leg can shift in the fingers while it is being turned upright. Any pose you compute from the grasp (where to put the gripper so the leg lands somewhere) is only as good as your last measurement of both poses, so re-read get_object_pose and get_ee_pose after reorienting and before committing to a placement or a descent. - Dropping the leg into its socket is unreliable -- even the benchmark's reference policy only lands it about half the time. After releasing, read the leg's pose: if it is not standing nearly upright within about a centimetre of the target, pick it up and place it again before you try to screw it. Screwing a leg that is beside the hole or lying down only knocks it around. - The wrist cannot rotate indefinitely, so a long turn has to be broken up: turn while gripping, open the gripper, rotate the empty gripper back, grip again, turn again. - get_object_pose(leg) reads the leg's live pose, so your code can compare it against the target from get_assembly_slots and keep turning until it matches instead of guessing a fixed number of turns. Assembly is registered at roughly 5-10 mm of position error with every axis of the leg within about 20 degrees of the target orientation. - Any move that ends in contact -- lowering the leg toward the hole, regrasping a seated leg, pressing it down while turning -- should use goto_pose(..., compliant=True). The default mode pushes to reach its target exactly and will shove a part that touches a rim, tilting it. - z_approach offsets along the gripper's own approach axis, so once the gripper is tilted, use explicit waypoints if you want a purely vertical descent. The functions (APIs) below are already imported into the environment. If you want to use numpy, you must import it explicitly. You may write python comments for reasoning but write ONLY executable Python code, with no code fences. APIs: get_object_pose(object_name: 'str', return_bbox_extent: 'bool' = False) Doc: Get the pose of a furniture part. Args: object_name: Part name, e.g. "square_table_top" or "square_table_leg4". return_bbox_extent: If True, also return the part's size. Returns: position: (3,) XYZ in metres, robot base frame. quaternion_wxyz: (4,) WXYZ unit quaternion. bbox_extent: only when return_bbox_extent is True -- (3,) full side lengths in metres along the part's OWN x, y, z axes (not the world axes), so a square table leg reads as (0.030, 0.0875, 0.030): 8.75 cm long with a 3 cm square cross-section. list_parts() -> 'list[str]' Doc: List the furniture parts present in the scene. Returns: List of part names accepted by get_object_pose / sample_grasp_pose. get_target_assembly() -> 'list[tuple[str, str]]' Doc: Get the part pairs that must be joined for this furniture to count as assembled. Only these pairs are scored. For the square table that is exactly one pair: the tabletop and one specific leg. Seating a different leg looks right but scores nothing, so read this first and work on the part it names. Returns: List of (base_part_name, moving_part_name) tuples. sample_grasp_pose(object_name: 'str') -> 'tuple[np.ndarray, np.ndarray]' Doc: Get a top-down grasp pose for a furniture part. The gripper points down at the table and its fingers are aligned with the part's yaw, so they close across the part rather than along it. Args: object_name: Part name. Returns: position: (3,) XYZ in metres, robot base frame -- the middle of the part, already lifted clear of the table surface, so it can be passed straight to goto_pose. quaternion_wxyz: (4,) WXYZ unit quaternion for the gripper. get_assembly_slots(base_part_name: 'str') -> 'list[tuple[np.ndarray, np.ndarray]]' Doc: Get the poses the mounted part must reach for the assembly to count. These are full target POSES for the part itself, not hole locations on the base part: drive the part so that get_object_pose(part) returns one of these, and the benchmark scores it. Do not add any offset of your own for the part's length -- the pose already accounts for it. For the square table there are four, one per corner. Args: base_part_name: The part the others attach to, e.g. "square_table_top". Returns: List of (position, quaternion_wxyz): position (3,) XYZ in metres, robot base frame; quaternion (4,) WXYZ, the orientation the mounted part must end up in. get_ee_pose() -> 'tuple[np.ndarray, np.ndarray]' Doc: Get the current end-effector pose. Returns: position: (3,) XYZ in metres, robot base frame. quaternion_wxyz: (4,) WXYZ unit quaternion. goto_pose(position: 'np.ndarray', quaternion_wxyz: 'np.ndarray', z_approach: 'float' = 0.0, compliant: 'bool' = False) -> 'None' Doc: Move the end-effector to a pose. Blocking. Args: position: (3,) XYZ in metres, robot base frame. quaternion_wxyz: (4,) WXYZ unit quaternion. z_approach: If non-zero, first move to `z_approach` metres above the target along the gripper's approach axis, then descend to it. Useful for grasps and insertions. compliant: Use for the final centimetres of a move that will end in contact (lowering a part into a hole, pressing it down). The default controller pushes hard to cancel its own sag, which shoves a part that touches a rim and tilts it in the grasp; compliant mode moves in small steps, turns slowly, stops when close enough, and yields to contact instead. open_gripper() -> 'None' Doc: Open the gripper. Blocking. close_gripper() -> 'None' Doc: Close the gripper to grasp. Blocking. home_pose() -> 'None' Doc: Move the arm back to a neutral pose above the workspace centre. Blocking.