@@ -1604,6 +1604,62 @@ def test_set_root_pose(batch_fixed_verts, relative, show_viewer, tol):
16041604 quat_ref = quat_delta
16051605 assert_allclose (quat , quat_ref , tol = tol )
16061606
1607+ if relative :
1608+ quat_rel_ref = quat_delta
1609+ else :
1610+ quat_rel_ref = gu .transform_quat_by_quat (gu .inv_quat (quat_zero ), quat_delta )
1611+ assert_allclose (entity .get_quat (relative = True ), quat_rel_ref , tol = tol )
1612+ # Verify get_quat(relative=False) matches get_quat() (preserves old behavior)
1613+ assert_allclose (entity .get_quat (relative = False ), quat , tol = tol )
1614+
1615+
1616+ @pytest .mark .required
1617+ def test_get_quat_relative_heterogeneous_initial_quat (show_viewer , tol ):
1618+ scene = gs .Scene (
1619+ rigid_options = gs .options .RigidOptions (batch_links_info = True ),
1620+ show_viewer = show_viewer ,
1621+ show_FPS = False ,
1622+ )
1623+ box = scene .add_entity (
1624+ morph = (
1625+ gs .morphs .Box (size = (0.04 , 0.04 , 0.04 ), pos = (0.0 , 0.0 , 0.1 ), euler = (0.0 , 0.0 , 0.0 )),
1626+ gs .morphs .Box (size = (0.04 , 0.04 , 0.04 ), pos = (0.0 , 0.0 , 0.1 ), euler = (0.0 , 45.0 , 0.0 )),
1627+ ),
1628+ )
1629+ scene .build (n_envs = 4 )
1630+
1631+ quat_delta = torch .tensor (
1632+ [
1633+ [0.9238795 , 0.3826834 , 0.0 , 0.0 ],
1634+ [0.8660254 , 0.0 , 0.5 , 0.0 ],
1635+ [0.7071068 , 0.0 , 0.0 , 0.7071068 ],
1636+ [1.0 , 0.0 , 0.0 , 0.0 ],
1637+ ],
1638+ dtype = gs .tc_float ,
1639+ device = gs .device ,
1640+ )
1641+ quat_delta = quat_delta / torch .linalg .norm (quat_delta , dim = - 1 , keepdim = True )
1642+
1643+ box .set_quat (quat_delta , relative = True )
1644+
1645+ assert_allclose (box .get_quat (relative = True ), quat_delta , tol = tol )
1646+ assert_allclose (box .get_quat (envs_idx = [2 , 3 ], relative = True ), quat_delta [2 :], tol = tol )
1647+
1648+
1649+ @pytest .mark .required
1650+ def test_get_quat_relative_non_parallel (show_viewer , tol ):
1651+ scene = gs .Scene (show_viewer = show_viewer , show_FPS = False )
1652+ box = scene .add_entity (gs .morphs .Box (size = (0.04 , 0.04 , 0.04 ), pos = (0.0 , 0.0 , 0.1 ), euler = (0.0 , 30.0 , 0.0 )))
1653+ scene .build ()
1654+
1655+ quat_delta = torch .tensor ([0.9238795 , 0.0 , 0.3826834 , 0.0 ], dtype = gs .tc_float , device = gs .device )
1656+ quat_delta = quat_delta / torch .linalg .norm (quat_delta )
1657+
1658+ box .set_quat (quat_delta , relative = True )
1659+ quat_rel = box .get_quat (relative = True )
1660+ assert quat_rel .shape == quat_delta .shape
1661+ assert_allclose (quat_rel , quat_delta , tol = tol )
1662+
16071663
16081664@pytest .mark .required
16091665def test_normalized_quat (show_viewer , tol ):
@@ -5062,6 +5118,10 @@ def test_merge_entities(is_fixed, merge_fixed_links, show_viewer, tol, monkeypat
50625118
50635119 attach_link = franka .get_link ("attachment" )
50645120 assert_allclose (attach_link .get_pos (), hand .links [0 ].get_pos (), tol = gs .EPS )
5121+ hand_quat_rel = hand .get_quat (relative = True )
5122+ hand_init_quat = torch .as_tensor (hand .base_link .quat , dtype = gs .tc_float , device = gs .device )
5123+ hand_quat_rel_ref = gu .transform_quat_by_quat (gu .inv_quat (hand_init_quat ), hand .get_quat ())
5124+ assert_allclose (hand_quat_rel , hand_quat_rel_ref , tol = tol )
50655125 offset_quat = gu .transform_quat_by_quat (hand .links [0 ].get_quat (), gu .inv_quat (attach_link .get_quat ()))
50665126 assert_allclose (gu .quat_to_xyz (offset_quat , rpy = False , degrees = True ), EULER_OFFSET , tol = tol )
50675127 for link in hand .links [slice (0 , None ) if merge_fixed_links else slice (1 , - 1 )]:
0 commit comments